Remove all styled(Component) patterns in favor of parent wrappers and props (#18430)
## Summary
Eliminates all ~350 `styled(Component)` usages across `twenty-front` and
`twenty-ui` (212 files changed). Each was replaced following these
rules:
- **Margin/layout CSS** (margin, padding, flex, align-self, width) →
wrapped in a `styled.div`/`styled.span` parent container
- **Third-party components** (Link, TextareaAutosize,
ReactPhoneNumberInput, Handle, etc.) → parent container with child CSS
selectors (`> a`, `> textarea`, `> input`, etc.)
- **Intrinsic behavior via existing props** (TableRow
`gridTemplateColumns`, TableCell `color`/`align`) → replaced
`styled(TableRow)` / `styled(TableCell)` with direct prop usage
- **Other visual overrides on twenty-ui components** (Card, Section,
TabList, Button, MenuItem, ScrollWrapper, etc.) → parent wrappers with
`> div` / `> *` child selectors
- **Extending styled.div/span** → merged all CSS into a single
`styled.div`/`styled.span`
Also adds `overflow: hidden` to parent containers wrapping
`ScrollWrapper` so scroll activates correctly with the new wrapper
structure.
### Migration patterns
| Before | After |
|--------|-------|
| `styled(Avatar)` with `margin-right` | `<StyledAvatarContainer><Avatar
/></StyledAvatarContainer>` |
| `styled(Link)` with `text-decoration: none` |
`<StyledLinkContainer><Link /></StyledLinkContainer>` with `> a { ... }`
|
| `styled(TableRow)` with `grid-template-columns` | `<TableRow
gridTemplateColumns="..." />` |
| `styled(TableCell)` with `color` / `align` | `<TableCell color={...}
align="right" />` |
| `styled(Card)` with `margin-top` | `<StyledCardContainer><Card
/></StyledCardContainer>` |
| `styled(TabList)` with `background` |
`<StyledTabListContainer><TabList /></StyledTabListContainer>` with `>
div { ... }` |
| `styled(StyledBase)` extending a `styled.div` | Single merged
`styled.div` with all styles inlined |
This commit is contained in:
+25
-21
@@ -13,11 +13,11 @@ type SettingsAccountsBlocklistTableProps = {
|
||||
handleBlockedEmailRemove: (id: string) => void;
|
||||
};
|
||||
|
||||
const StyledTable = styled(Table)`
|
||||
const StyledTableContainer = styled.div`
|
||||
margin-top: ${themeCssVariables.spacing[4]};
|
||||
`;
|
||||
|
||||
const StyledTableBody = styled(TableBody)`
|
||||
const StyledTableBodyContainer = styled.div`
|
||||
border-bottom: 1px solid ${themeCssVariables.border.color.light};
|
||||
`;
|
||||
|
||||
@@ -28,25 +28,29 @@ export const SettingsAccountsBlocklistTable = ({
|
||||
return (
|
||||
<>
|
||||
{blocklist.length > 0 && (
|
||||
<StyledTable>
|
||||
<TableRow
|
||||
gridAutoColumns="200px 1fr 20px"
|
||||
mobileGridAutoColumns="120px 1fr 20px"
|
||||
>
|
||||
<TableHeader>{t`Email/Domain`}</TableHeader>
|
||||
<TableHeader>{t`Added to blocklist`}</TableHeader>
|
||||
<TableHeader></TableHeader>
|
||||
</TableRow>
|
||||
<StyledTableBody>
|
||||
{blocklist.map((blocklistItem) => (
|
||||
<SettingsAccountsBlocklistTableRow
|
||||
key={blocklistItem.id}
|
||||
blocklistItem={blocklistItem}
|
||||
onRemove={handleBlockedEmailRemove}
|
||||
/>
|
||||
))}
|
||||
</StyledTableBody>
|
||||
</StyledTable>
|
||||
<StyledTableContainer>
|
||||
<Table>
|
||||
<TableRow
|
||||
gridAutoColumns="200px 1fr 20px"
|
||||
mobileGridAutoColumns="120px 1fr 20px"
|
||||
>
|
||||
<TableHeader>{t`Email/Domain`}</TableHeader>
|
||||
<TableHeader>{t`Added to blocklist`}</TableHeader>
|
||||
<TableHeader></TableHeader>
|
||||
</TableRow>
|
||||
<StyledTableBodyContainer>
|
||||
<TableBody>
|
||||
{blocklist.map((blocklistItem) => (
|
||||
<SettingsAccountsBlocklistTableRow
|
||||
key={blocklistItem.id}
|
||||
blocklistItem={blocklistItem}
|
||||
onRemove={handleBlockedEmailRemove}
|
||||
/>
|
||||
))}
|
||||
</TableBody>
|
||||
</StyledTableBodyContainer>
|
||||
</Table>
|
||||
</StyledTableContainer>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
|
||||
+14
-4
@@ -11,8 +11,10 @@ type SettingsAccountsEventVisibilitySettingsCardProps = {
|
||||
value?: CalendarChannelVisibility;
|
||||
};
|
||||
|
||||
const StyledCardMedia = styled(SettingsAccountsVisibilityIcon)`
|
||||
height: ${themeCssVariables.spacing[6]};
|
||||
const StyledCardMediaContainer = styled.div`
|
||||
> * {
|
||||
height: ${themeCssVariables.spacing[6]};
|
||||
}
|
||||
`;
|
||||
|
||||
const eventSettingsVisibilityOptions = [
|
||||
@@ -20,13 +22,21 @@ const eventSettingsVisibilityOptions = [
|
||||
title: msg`Everything`,
|
||||
description: msg`The whole event details will be shared with your team.`,
|
||||
value: CalendarChannelVisibility.SHARE_EVERYTHING,
|
||||
cardMedia: <StyledCardMedia subject="active" body="active" />,
|
||||
cardMedia: (
|
||||
<StyledCardMediaContainer>
|
||||
<SettingsAccountsVisibilityIcon subject="active" body="active" />
|
||||
</StyledCardMediaContainer>
|
||||
),
|
||||
},
|
||||
{
|
||||
title: msg`Metadata`,
|
||||
description: msg`Only date & participants will be shared with your team.`,
|
||||
value: CalendarChannelVisibility.METADATA,
|
||||
cardMedia: <StyledCardMedia subject="active" body="inactive" />,
|
||||
cardMedia: (
|
||||
<StyledCardMediaContainer>
|
||||
<SettingsAccountsVisibilityIcon subject="active" body="inactive" />
|
||||
</StyledCardMediaContainer>
|
||||
),
|
||||
},
|
||||
];
|
||||
|
||||
|
||||
+18
-14
@@ -19,11 +19,13 @@ const StyledTableRows = styled.div`
|
||||
padding-top: ${themeCssVariables.spacing[2]};
|
||||
`;
|
||||
|
||||
const StyledAddAccountSection = styled(Section)`
|
||||
border-top: 1px solid ${themeCssVariables.border.color.light};
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
padding-top: ${themeCssVariables.spacing[2]};
|
||||
const StyledAddAccountSectionContainer = styled.div`
|
||||
> * {
|
||||
border-top: 1px solid ${themeCssVariables.border.color.light};
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
padding-top: ${themeCssVariables.spacing[2]};
|
||||
}
|
||||
`;
|
||||
|
||||
export const SettingsAccountsConnectedAccountsListCard = ({
|
||||
@@ -51,15 +53,17 @@ export const SettingsAccountsConnectedAccountsListCard = ({
|
||||
))}
|
||||
</StyledTableRows>
|
||||
</Table>
|
||||
<StyledAddAccountSection>
|
||||
<Button
|
||||
Icon={IconPlus}
|
||||
title={t`Add account`}
|
||||
variant="secondary"
|
||||
size="small"
|
||||
onClick={() => navigateSettings(SettingsPath.NewAccount)}
|
||||
/>
|
||||
</StyledAddAccountSection>
|
||||
<StyledAddAccountSectionContainer>
|
||||
<Section>
|
||||
<Button
|
||||
Icon={IconPlus}
|
||||
title={t`Add account`}
|
||||
variant="secondary"
|
||||
size="small"
|
||||
onClick={() => navigateSettings(SettingsPath.NewAccount)}
|
||||
/>
|
||||
</Section>
|
||||
</StyledAddAccountSectionContainer>
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
|
||||
+11
-2
@@ -1,6 +1,5 @@
|
||||
import { styled } from '@linaria/react';
|
||||
|
||||
import { SettingsAccountsCardMedia } from '@/settings/accounts/components/SettingsAccountsCardMedia';
|
||||
import { themeCssVariables } from 'twenty-ui/theme-constants';
|
||||
|
||||
type SettingsAccountsMessageAutoCreationIconProps = {
|
||||
@@ -9,8 +8,18 @@ type SettingsAccountsMessageAutoCreationIconProps = {
|
||||
isReceivedActive?: boolean;
|
||||
};
|
||||
|
||||
const StyledIconContainer = styled(SettingsAccountsCardMedia)`
|
||||
const StyledIconContainer = styled.div`
|
||||
align-items: stretch;
|
||||
border: 2px solid ${themeCssVariables.border.color.medium};
|
||||
border-radius: ${themeCssVariables.border.radius.sm};
|
||||
color: ${themeCssVariables.font.color.light};
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: ${themeCssVariables.spacing['0.5']};
|
||||
height: ${themeCssVariables.spacing[8]};
|
||||
justify-content: center;
|
||||
padding: ${themeCssVariables.spacing['0.5']};
|
||||
width: ${themeCssVariables.spacing[6]};
|
||||
`;
|
||||
|
||||
const StyledDirectionSkeleton = styled.div<{ isActive?: boolean }>`
|
||||
|
||||
+10
-2
@@ -1,7 +1,6 @@
|
||||
import { styled } from '@linaria/react';
|
||||
|
||||
import { MessageFolderImportPolicy } from '@/accounts/types/MessageChannel';
|
||||
import { SettingsAccountsCardMedia } from '@/settings/accounts/components/SettingsAccountsCardMedia';
|
||||
import { themeCssVariables } from 'twenty-ui/theme-constants';
|
||||
|
||||
type SettingsAccountsMessageFolderIconProps = {
|
||||
@@ -9,9 +8,18 @@ type SettingsAccountsMessageFolderIconProps = {
|
||||
value?: MessageFolderImportPolicy;
|
||||
};
|
||||
|
||||
const StyledCardMedia = styled(SettingsAccountsCardMedia)`
|
||||
const StyledCardMedia = styled.div`
|
||||
align-items: stretch;
|
||||
border: 2px solid ${themeCssVariables.border.color.medium};
|
||||
border-radius: ${themeCssVariables.border.radius.sm};
|
||||
color: ${themeCssVariables.font.color.light};
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: ${themeCssVariables.spacing['0.5']};
|
||||
height: ${themeCssVariables.spacing[8]};
|
||||
justify-content: center;
|
||||
padding: ${themeCssVariables.spacing['0.5']};
|
||||
width: ${themeCssVariables.spacing[6]};
|
||||
`;
|
||||
|
||||
const StyledFolderRow = styled.div`
|
||||
|
||||
+40
-33
@@ -14,11 +14,13 @@ type SettingsAccountsRadioSettingsCardProps<Option extends { value: string }> =
|
||||
name: string;
|
||||
};
|
||||
|
||||
const StyledCardContent = styled(CardContent)`
|
||||
cursor: pointer;
|
||||
const StyledCardContentContainer = styled.div`
|
||||
> * {
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
background: ${themeCssVariables.background.transparent.lighter};
|
||||
&:hover {
|
||||
background: ${themeCssVariables.background.transparent.lighter};
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -39,8 +41,10 @@ const StyledDescription = styled.div`
|
||||
font-size: ${themeCssVariables.font.size.sm};
|
||||
`;
|
||||
|
||||
const StyledRadio = styled(Radio)`
|
||||
const StyledRadioContainer = styled.span`
|
||||
margin-left: auto;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
`;
|
||||
|
||||
const StyledExpandedContent = styled.div`
|
||||
@@ -63,34 +67,37 @@ export const SettingsAccountsRadioSettingsCard = <
|
||||
}: SettingsAccountsRadioSettingsCardProps<Option>) => (
|
||||
<Card rounded>
|
||||
{options.map((option, index) => (
|
||||
<StyledCardContent
|
||||
key={option.value}
|
||||
divider={index < options.length - 1}
|
||||
onClick={() => onChange(option.value)}
|
||||
>
|
||||
<StyledOptionHeader>
|
||||
{option.cardMedia}
|
||||
<div>
|
||||
<StyledTitle>
|
||||
<Trans id={option.title.id} />
|
||||
</StyledTitle>
|
||||
<StyledDescription>
|
||||
<Trans id={option.description.id} />
|
||||
</StyledDescription>
|
||||
</div>
|
||||
<StyledRadio
|
||||
name={name}
|
||||
value={option.value}
|
||||
onCheckedChange={() => onChange(option.value)}
|
||||
checked={value === option.value}
|
||||
/>
|
||||
</StyledOptionHeader>
|
||||
{option.cardContentExpanded && value === option.value && (
|
||||
<StyledExpandedContent>
|
||||
{option.cardContentExpanded}
|
||||
</StyledExpandedContent>
|
||||
)}
|
||||
</StyledCardContent>
|
||||
<StyledCardContentContainer key={option.value}>
|
||||
<CardContent
|
||||
divider={index < options.length - 1}
|
||||
onClick={() => onChange(option.value)}
|
||||
>
|
||||
<StyledOptionHeader>
|
||||
{option.cardMedia}
|
||||
<div>
|
||||
<StyledTitle>
|
||||
<Trans id={option.title.id} />
|
||||
</StyledTitle>
|
||||
<StyledDescription>
|
||||
<Trans id={option.description.id} />
|
||||
</StyledDescription>
|
||||
</div>
|
||||
<StyledRadioContainer>
|
||||
<Radio
|
||||
name={name}
|
||||
value={option.value}
|
||||
onCheckedChange={() => onChange(option.value)}
|
||||
checked={value === option.value}
|
||||
/>
|
||||
</StyledRadioContainer>
|
||||
</StyledOptionHeader>
|
||||
{option.cardContentExpanded && value === option.value && (
|
||||
<StyledExpandedContent>
|
||||
{option.cardContentExpanded}
|
||||
</StyledExpandedContent>
|
||||
)}
|
||||
</CardContent>
|
||||
</StyledCardContentContainer>
|
||||
))}
|
||||
</Card>
|
||||
);
|
||||
|
||||
+26
-19
@@ -14,14 +14,16 @@ type SettingsAccountsToggleSettingCardProps = {
|
||||
parameters: Parameter[];
|
||||
};
|
||||
|
||||
const StyledCardContent = styled(CardContent)`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
gap: ${themeCssVariables.spacing[4]};
|
||||
cursor: pointer;
|
||||
const StyledCardContentContainer = styled.div`
|
||||
> * {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
gap: ${themeCssVariables.spacing[4]};
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
background: ${themeCssVariables.background.transparent.lighter};
|
||||
&:hover {
|
||||
background: ${themeCssVariables.background.transparent.lighter};
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -36,8 +38,10 @@ const StyledDescription = styled.div`
|
||||
font-size: ${themeCssVariables.font.size.sm};
|
||||
`;
|
||||
|
||||
const StyledToggle = styled(Toggle)`
|
||||
const StyledToggleContainer = styled.span`
|
||||
margin-left: auto;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
`;
|
||||
|
||||
export const SettingsAccountsToggleSettingCard = ({
|
||||
@@ -45,17 +49,20 @@ export const SettingsAccountsToggleSettingCard = ({
|
||||
}: SettingsAccountsToggleSettingCardProps) => (
|
||||
<Card rounded>
|
||||
{parameters.map((parameter, index) => (
|
||||
<StyledCardContent
|
||||
key={index}
|
||||
divider={index < parameters.length - 1}
|
||||
onClick={() => parameter.onToggle(!parameter.value)}
|
||||
>
|
||||
<div>
|
||||
<StyledTitle>{parameter.title}</StyledTitle>
|
||||
<StyledDescription>{parameter.description}</StyledDescription>
|
||||
</div>
|
||||
<StyledToggle value={parameter.value} onChange={parameter.onToggle} />
|
||||
</StyledCardContent>
|
||||
<StyledCardContentContainer key={index}>
|
||||
<CardContent
|
||||
divider={index < parameters.length - 1}
|
||||
onClick={() => parameter.onToggle(!parameter.value)}
|
||||
>
|
||||
<div>
|
||||
<StyledTitle>{parameter.title}</StyledTitle>
|
||||
<StyledDescription>{parameter.description}</StyledDescription>
|
||||
</div>
|
||||
<StyledToggleContainer>
|
||||
<Toggle value={parameter.value} onChange={parameter.onToggle} />
|
||||
</StyledToggleContainer>
|
||||
</CardContent>
|
||||
</StyledCardContentContainer>
|
||||
))}
|
||||
</Card>
|
||||
);
|
||||
|
||||
+24
-4
@@ -1,6 +1,5 @@
|
||||
import { styled } from '@linaria/react';
|
||||
|
||||
import { SettingsAccountsCardMedia } from '@/settings/accounts/components/SettingsAccountsCardMedia';
|
||||
import { themeCssVariables } from 'twenty-ui/theme-constants';
|
||||
|
||||
type VisibilityElementState = 'active' | 'inactive';
|
||||
@@ -12,8 +11,18 @@ type SettingsAccountsVisibilityIconProps = {
|
||||
body?: VisibilityElementState;
|
||||
};
|
||||
|
||||
const StyledCardMedia = styled(SettingsAccountsCardMedia)`
|
||||
const StyledCardMedia = styled.div`
|
||||
align-items: stretch;
|
||||
border: 2px solid ${themeCssVariables.border.color.medium};
|
||||
border-radius: ${themeCssVariables.border.radius.sm};
|
||||
color: ${themeCssVariables.font.color.light};
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: ${themeCssVariables.spacing['0.5']};
|
||||
height: ${themeCssVariables.spacing[8]};
|
||||
justify-content: center;
|
||||
padding: ${themeCssVariables.spacing['0.5']};
|
||||
width: ${themeCssVariables.spacing[6]};
|
||||
`;
|
||||
|
||||
const StyledSubjectSkeleton = styled.div<{ isActive?: boolean }>`
|
||||
@@ -25,13 +34,24 @@ const StyledSubjectSkeleton = styled.div<{ isActive?: boolean }>`
|
||||
height: 3px;
|
||||
`;
|
||||
|
||||
const StyledMetadataSkeleton = styled(StyledSubjectSkeleton)`
|
||||
const StyledMetadataSkeleton = styled.div<{ isActive?: boolean }>`
|
||||
background-color: ${({ isActive }) =>
|
||||
isActive
|
||||
? themeCssVariables.accent.accent4060
|
||||
: themeCssVariables.background.quaternary};
|
||||
border-radius: 1px;
|
||||
height: 3px;
|
||||
margin-right: ${themeCssVariables.spacing[2]};
|
||||
`;
|
||||
|
||||
const StyledBodySkeleton = styled(StyledSubjectSkeleton)`
|
||||
const StyledBodySkeleton = styled.div<{ isActive?: boolean }>`
|
||||
background-color: ${({ isActive }) =>
|
||||
isActive
|
||||
? themeCssVariables.accent.accent4060
|
||||
: themeCssVariables.background.quaternary};
|
||||
border-radius: ${themeCssVariables.border.radius.xs};
|
||||
flex: 1 0 auto;
|
||||
height: 3px;
|
||||
`;
|
||||
|
||||
export const SettingsAccountsVisibilityIcon = ({
|
||||
|
||||
+9
-9
@@ -1,24 +1,24 @@
|
||||
import { Table } from '@/ui/layout/table/components/Table';
|
||||
import { TableHeader } from '@/ui/layout/table/components/TableHeader';
|
||||
import { TableRow } from '@/ui/layout/table/components/TableRow';
|
||||
import { styled } from '@linaria/react';
|
||||
import { Trans } from '@lingui/react/macro';
|
||||
import { themeCssVariables } from 'twenty-ui/theme-constants';
|
||||
|
||||
const StyledTableHeader = styled(TableHeader)`
|
||||
padding-right: ${themeCssVariables.spacing[14]};
|
||||
`;
|
||||
|
||||
export const SettingsConnectedAccountsTableHeader = () => {
|
||||
return (
|
||||
<Table>
|
||||
<TableRow gridAutoColumns="332px 1fr">
|
||||
<StyledTableHeader>
|
||||
<TableHeader
|
||||
padding={`0 ${themeCssVariables.spacing[14]} 0 ${themeCssVariables.spacing[2]}`}
|
||||
>
|
||||
<Trans>Account</Trans>
|
||||
</StyledTableHeader>
|
||||
<StyledTableHeader align="right">
|
||||
</TableHeader>
|
||||
<TableHeader
|
||||
align="right"
|
||||
padding={`0 ${themeCssVariables.spacing[14]} 0 ${themeCssVariables.spacing[2]}`}
|
||||
>
|
||||
<Trans>Status</Trans>
|
||||
</StyledTableHeader>
|
||||
</TableHeader>
|
||||
</TableRow>
|
||||
</Table>
|
||||
);
|
||||
|
||||
+20
-18
@@ -37,18 +37,11 @@ const StyledFoldersContainer = styled.div`
|
||||
padding-top: ${themeCssVariables.spacing[2]};
|
||||
`;
|
||||
|
||||
const StyledSearchInput = styled(SettingsTextInput)`
|
||||
const StyledSearchInputContainer = styled.div`
|
||||
margin-bottom: ${themeCssVariables.spacing[2]};
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
const StyledCheckboxCell = styled(TableCell)`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
padding-right: ${themeCssVariables.spacing[1]};
|
||||
justify-content: flex-end;
|
||||
`;
|
||||
|
||||
const StyledSectionHeader = styled.div`
|
||||
align-items: center;
|
||||
background-color: ${themeCssVariables.background.transparent.lighter};
|
||||
@@ -61,8 +54,10 @@ const StyledSectionHeader = styled.div`
|
||||
text-align: left;
|
||||
`;
|
||||
|
||||
const StyledLabel = styled(Label)`
|
||||
const StyledLabelContainer = styled.span`
|
||||
align-items: center;
|
||||
color: ${themeCssVariables.font.color.tertiary};
|
||||
display: flex;
|
||||
margin-bottom: ${themeCssVariables.spacing[2]};
|
||||
margin-top: ${themeCssVariables.spacing[2]};
|
||||
`;
|
||||
@@ -165,23 +160,30 @@ export const SettingsAccountsMessageFoldersCard = () => {
|
||||
return (
|
||||
<Section>
|
||||
<Table>
|
||||
<StyledSearchInput
|
||||
placeholder={t`Search folders...`}
|
||||
value={search}
|
||||
onChange={setSearch}
|
||||
instanceId={'message-folders-search'}
|
||||
/>
|
||||
<StyledLabel>{t`Folders`}</StyledLabel>
|
||||
<StyledSearchInputContainer>
|
||||
<SettingsTextInput
|
||||
placeholder={t`Search folders...`}
|
||||
value={search}
|
||||
onChange={setSearch}
|
||||
instanceId={'message-folders-search'}
|
||||
/>
|
||||
</StyledSearchInputContainer>
|
||||
<StyledLabelContainer>
|
||||
<Label>{t`Folders`}</Label>
|
||||
</StyledLabelContainer>
|
||||
|
||||
<StyledSectionHeader>
|
||||
<Label>{t`Toggle all folders`}</Label>
|
||||
<StyledCheckboxCell>
|
||||
<TableCell
|
||||
align="right"
|
||||
padding={`0 ${themeCssVariables.spacing[1]} 0 ${themeCssVariables.spacing[2]}`}
|
||||
>
|
||||
<Checkbox
|
||||
checked={allFoldersToggled}
|
||||
onChange={() => handleToggleAllFolders(messageFolders)}
|
||||
size={CheckboxSize.Small}
|
||||
/>
|
||||
</StyledCheckboxCell>
|
||||
</TableCell>
|
||||
</StyledSectionHeader>
|
||||
|
||||
<StyledFoldersContainer>
|
||||
|
||||
+10
-8
@@ -37,7 +37,7 @@ const StyledSearchAndFilterContainer = styled.div`
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
const StyledSearchInput = styled(SettingsTextInput)`
|
||||
const StyledSearchInputContainer = styled.div`
|
||||
flex: 1;
|
||||
`;
|
||||
|
||||
@@ -163,13 +163,15 @@ export const SettingsAdminAI = () => {
|
||||
/>
|
||||
|
||||
<StyledSearchAndFilterContainer>
|
||||
<StyledSearchInput
|
||||
instanceId="admin-model-search"
|
||||
LeftIcon={IconSearch}
|
||||
placeholder={t`Search a model...`}
|
||||
value={searchQuery}
|
||||
onChange={setSearchQuery}
|
||||
/>
|
||||
<StyledSearchInputContainer>
|
||||
<SettingsTextInput
|
||||
instanceId="admin-model-search"
|
||||
LeftIcon={IconSearch}
|
||||
placeholder={t`Search a model...`}
|
||||
value={searchQuery}
|
||||
onChange={setSearchQuery}
|
||||
/>
|
||||
</StyledSearchInputContainer>
|
||||
<Dropdown
|
||||
dropdownId="admin-ai-models-filter-dropdown"
|
||||
dropdownPlacement="bottom-end"
|
||||
|
||||
+11
-9
@@ -3,7 +3,7 @@ import { styled } from '@linaria/react';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { IconSearch } from 'twenty-ui/display';
|
||||
|
||||
const StyledSearchInput = styled(SettingsTextInput)`
|
||||
const StyledSearchInputContainer = styled.div`
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
@@ -17,13 +17,15 @@ export const ConfigVariableSearchInput = ({
|
||||
onChange,
|
||||
}: ConfigVariableSearchInputProps) => {
|
||||
return (
|
||||
<StyledSearchInput
|
||||
instanceId="config-variable-search"
|
||||
placeholder={t`Search config variables`}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
autoFocus={false}
|
||||
LeftIcon={IconSearch}
|
||||
/>
|
||||
<StyledSearchInputContainer>
|
||||
<SettingsTextInput
|
||||
instanceId="config-variable-search"
|
||||
placeholder={t`Search config variables`}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
autoFocus={false}
|
||||
LeftIcon={IconSearch}
|
||||
/>
|
||||
</StyledSearchInputContainer>
|
||||
);
|
||||
};
|
||||
|
||||
+38
-29
@@ -12,16 +12,11 @@ type SettingsAdminConfigVariablesRowProps = {
|
||||
variable: ConfigVariable;
|
||||
};
|
||||
|
||||
const StyledTruncatedCell = styled(TableCell)`
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
cursor: pointer;
|
||||
`;
|
||||
|
||||
const StyledTableRow = styled(TableRow)`
|
||||
&:hover {
|
||||
background-color: ${themeCssVariables.background.transparent.light};
|
||||
const StyledTableRowContainer = styled.div`
|
||||
> * {
|
||||
&:hover {
|
||||
background-color: ${themeCssVariables.background.transparent.light};
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -48,24 +43,38 @@ export const SettingsAdminConfigVariablesRow = ({
|
||||
: variable.value;
|
||||
|
||||
return (
|
||||
<StyledTableRow
|
||||
gridAutoColumns="5fr 3fr 1fr"
|
||||
to={getSettingsPath(SettingsPath.AdminPanelConfigVariableDetails, {
|
||||
variableName: variable.name,
|
||||
})}
|
||||
>
|
||||
<StyledTruncatedCell color={theme.font.color.primary}>
|
||||
<StyledEllipsisLabel>{variable.name}</StyledEllipsisLabel>
|
||||
</StyledTruncatedCell>
|
||||
<StyledTruncatedCell align="right">
|
||||
<StyledEllipsisLabel>{displayValue}</StyledEllipsisLabel>
|
||||
</StyledTruncatedCell>
|
||||
<TableCell align="right">
|
||||
<IconChevronRight
|
||||
size={theme.icon.size.md}
|
||||
color={theme.font.color.tertiary}
|
||||
/>
|
||||
</TableCell>
|
||||
</StyledTableRow>
|
||||
<StyledTableRowContainer>
|
||||
<TableRow
|
||||
gridAutoColumns="5fr 3fr 1fr"
|
||||
to={getSettingsPath(SettingsPath.AdminPanelConfigVariableDetails, {
|
||||
variableName: variable.name,
|
||||
})}
|
||||
>
|
||||
<TableCell
|
||||
color={theme.font.color.primary}
|
||||
whiteSpace="nowrap"
|
||||
overflow="hidden"
|
||||
textOverflow="ellipsis"
|
||||
clickable
|
||||
>
|
||||
<StyledEllipsisLabel>{variable.name}</StyledEllipsisLabel>
|
||||
</TableCell>
|
||||
<TableCell
|
||||
align="right"
|
||||
whiteSpace="nowrap"
|
||||
overflow="hidden"
|
||||
textOverflow="ellipsis"
|
||||
clickable
|
||||
>
|
||||
<StyledEllipsisLabel>{displayValue}</StyledEllipsisLabel>
|
||||
</TableCell>
|
||||
<TableCell align="right">
|
||||
<IconChevronRight
|
||||
size={theme.icon.size.md}
|
||||
color={theme.font.color.tertiary}
|
||||
/>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</StyledTableRowContainer>
|
||||
);
|
||||
};
|
||||
|
||||
+11
-9
@@ -8,7 +8,7 @@ import { styled } from '@linaria/react';
|
||||
import { themeCssVariables } from 'twenty-ui/theme-constants';
|
||||
import { type ConfigVariable } from '~/generated-metadata/graphql';
|
||||
|
||||
const StyledTableBody = styled(TableBody)`
|
||||
const StyledTableBodyContainer = styled.div`
|
||||
border-bottom: 1px solid ${themeCssVariables.border.color.light};
|
||||
`;
|
||||
|
||||
@@ -26,14 +26,16 @@ export const SettingsAdminConfigVariablesTable = ({
|
||||
<TableHeader align="right">{t`Value`}</TableHeader>
|
||||
<TableHeader align="right"></TableHeader>
|
||||
</TableRow>
|
||||
<StyledTableBody>
|
||||
{variables.map((variable) => (
|
||||
<SettingsAdminConfigVariablesRow
|
||||
key={variable.name}
|
||||
variable={variable}
|
||||
/>
|
||||
))}
|
||||
</StyledTableBody>
|
||||
<StyledTableBodyContainer>
|
||||
<TableBody>
|
||||
{variables.map((variable) => (
|
||||
<SettingsAdminConfigVariablesRow
|
||||
key={variable.name}
|
||||
variable={variable}
|
||||
/>
|
||||
))}
|
||||
</TableBody>
|
||||
</StyledTableBodyContainer>
|
||||
</Table>
|
||||
);
|
||||
};
|
||||
|
||||
+14
-10
@@ -5,9 +5,11 @@ import { H2Title } from 'twenty-ui/display';
|
||||
import { Section } from 'twenty-ui/layout';
|
||||
import { themeCssVariables } from 'twenty-ui/theme-constants';
|
||||
|
||||
const StyledSettingsAdminTableCard = styled(SettingsAdminTableCard)`
|
||||
padding-left: ${themeCssVariables.spacing[2]};
|
||||
padding-right: ${themeCssVariables.spacing[2]};
|
||||
const StyledSettingsAdminTableCardContainer = styled.div`
|
||||
> * {
|
||||
padding-left: ${themeCssVariables.spacing[2]};
|
||||
padding-right: ${themeCssVariables.spacing[2]};
|
||||
}
|
||||
`;
|
||||
|
||||
export const SettingsAdminHealthAccountSyncCountersTable = ({
|
||||
@@ -47,13 +49,15 @@ export const SettingsAdminHealthAccountSyncCountersTable = ({
|
||||
return (
|
||||
<Section>
|
||||
<H2Title title={title} description={description} />
|
||||
<StyledSettingsAdminTableCard
|
||||
items={items}
|
||||
rounded
|
||||
gridAutoColumns="1fr 1fr"
|
||||
labelAlign="left"
|
||||
valueAlign="right"
|
||||
/>
|
||||
<StyledSettingsAdminTableCardContainer>
|
||||
<SettingsAdminTableCard
|
||||
items={items}
|
||||
rounded
|
||||
gridAutoColumns="1fr 1fr"
|
||||
labelAlign="left"
|
||||
valueAlign="right"
|
||||
/>
|
||||
</StyledSettingsAdminTableCardContainer>
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
|
||||
+24
-20
@@ -29,9 +29,11 @@ const StyledNoDataMessage = styled.div`
|
||||
justify-content: center;
|
||||
`;
|
||||
|
||||
const StyledSettingsAdminTableCard = styled(SettingsAdminTableCard)`
|
||||
padding-left: ${themeCssVariables.spacing[2]};
|
||||
padding-right: ${themeCssVariables.spacing[2]};
|
||||
const StyledSettingsAdminTableCardContainer = styled.div`
|
||||
> * {
|
||||
padding-left: ${themeCssVariables.spacing[2]};
|
||||
padding-right: ${themeCssVariables.spacing[2]};
|
||||
}
|
||||
`;
|
||||
|
||||
type SettingsAdminWorkerMetricsGraphProps = {
|
||||
@@ -200,23 +202,25 @@ export const SettingsAdminWorkerMetricsGraph = ({
|
||||
)}
|
||||
</StyledGraphContainer>
|
||||
{metricsDetails && (
|
||||
<StyledSettingsAdminTableCard
|
||||
rounded
|
||||
items={Object.entries(metricsDetails)
|
||||
.filter(([key]) => key !== '__typename')
|
||||
.map(([key, value]) => ({
|
||||
label: key.charAt(0).toUpperCase() + key.slice(1),
|
||||
value:
|
||||
typeof value === 'number'
|
||||
? value
|
||||
: Array.isArray(value)
|
||||
? value.length
|
||||
: String(value),
|
||||
}))}
|
||||
gridAutoColumns="1fr 1fr"
|
||||
labelAlign="left"
|
||||
valueAlign="right"
|
||||
/>
|
||||
<StyledSettingsAdminTableCardContainer>
|
||||
<SettingsAdminTableCard
|
||||
rounded
|
||||
items={Object.entries(metricsDetails)
|
||||
.filter(([key]) => key !== '__typename')
|
||||
.map(([key, value]) => ({
|
||||
label: key.charAt(0).toUpperCase() + key.slice(1),
|
||||
value:
|
||||
typeof value === 'number'
|
||||
? value
|
||||
: Array.isArray(value)
|
||||
? value.length
|
||||
: String(value),
|
||||
}))}
|
||||
gridAutoColumns="1fr 1fr"
|
||||
labelAlign="left"
|
||||
valueAlign="right"
|
||||
/>
|
||||
</StyledSettingsAdminTableCardContainer>
|
||||
)}
|
||||
</>
|
||||
);
|
||||
|
||||
+10
-6
@@ -26,8 +26,10 @@ const StyledDotContainer = styled.div<{ dotPosition: DotPosition }>`
|
||||
dotPosition === 'top' ? 'stretch' : 'center'};
|
||||
`;
|
||||
|
||||
const StyledIconPoint = styled(IconPoint)`
|
||||
const StyledIconPointContainer = styled.span`
|
||||
margin-right: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
`;
|
||||
|
||||
export const AdvancedSettingsContentWrapperWithDot = ({
|
||||
@@ -41,11 +43,13 @@ export const AdvancedSettingsContentWrapperWithDot = ({
|
||||
<StyledWrapper>
|
||||
{!hideDot && (
|
||||
<StyledDotContainer dotPosition={dotPosition}>
|
||||
<StyledIconPoint
|
||||
size={12}
|
||||
color={theme.color.yellow}
|
||||
fill={theme.color.yellow}
|
||||
/>
|
||||
<StyledIconPointContainer>
|
||||
<IconPoint
|
||||
size={12}
|
||||
color={theme.color.yellow}
|
||||
fill={theme.color.yellow}
|
||||
/>
|
||||
</StyledIconPointContainer>
|
||||
</StyledDotContainer>
|
||||
)}
|
||||
{children}
|
||||
|
||||
@@ -18,28 +18,34 @@ type SettingsCardProps = {
|
||||
Status?: ReactNode;
|
||||
};
|
||||
|
||||
const StyledCard = styled(Card)<{
|
||||
const StyledCardWrapper = styled.div<{
|
||||
disabled?: boolean;
|
||||
onClick?: () => void;
|
||||
clickable?: boolean;
|
||||
}>`
|
||||
color: ${({ disabled }) =>
|
||||
disabled
|
||||
? themeCssVariables.font.color.extraLight
|
||||
: themeCssVariables.font.color.tertiary};
|
||||
cursor: ${({ disabled, onClick }) =>
|
||||
disabled ? 'not-allowed' : onClick ? 'pointer' : 'default'};
|
||||
cursor: ${({ disabled, clickable }) =>
|
||||
disabled ? 'not-allowed' : clickable ? 'pointer' : 'default'};
|
||||
width: 100%;
|
||||
|
||||
> * {
|
||||
color: inherit;
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledCardContent = styled(CardContent)`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: ${themeCssVariables.spacing[2]};
|
||||
padding: ${themeCssVariables.spacing[2]} ${themeCssVariables.spacing[2]};
|
||||
const StyledCardContentContainer = styled.div`
|
||||
> * {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: ${themeCssVariables.spacing[2]};
|
||||
padding: ${themeCssVariables.spacing[2]} ${themeCssVariables.spacing[2]};
|
||||
|
||||
&:hover {
|
||||
background-color: ${themeCssVariables.background.quaternary};
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
background-color: ${themeCssVariables.background.quaternary};
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -61,8 +67,10 @@ const StyledTitle = styled.div<{ disabled?: boolean }>`
|
||||
justify-content: flex-start;
|
||||
`;
|
||||
|
||||
const StyledIconChevronRight = styled(IconChevronRight)`
|
||||
const StyledIconChevronRightContainer = styled.span`
|
||||
align-items: center;
|
||||
color: ${themeCssVariables.font.color.light};
|
||||
display: flex;
|
||||
`;
|
||||
|
||||
const StyledDescription = styled.div`
|
||||
@@ -91,24 +99,31 @@ export const SettingsCard = ({
|
||||
const { theme } = useContext(ThemeContext);
|
||||
|
||||
return (
|
||||
<StyledCard
|
||||
<StyledCardWrapper
|
||||
disabled={disabled}
|
||||
onClick={disabled ? undefined : onClick}
|
||||
clickable={!!onClick}
|
||||
className={className}
|
||||
rounded={true}
|
||||
>
|
||||
<StyledCardContent>
|
||||
<StyledHeader>
|
||||
<StyledIconContainer>{Icon}</StyledIconContainer>
|
||||
<StyledTitle disabled={disabled}>
|
||||
{title}
|
||||
{soon && <Pill label={t`Soon`} />}
|
||||
</StyledTitle>
|
||||
{Status && Status}
|
||||
<StyledIconChevronRight size={theme.icon.size.sm} />
|
||||
</StyledHeader>
|
||||
{description && <StyledDescription>{description}</StyledDescription>}
|
||||
</StyledCardContent>
|
||||
</StyledCard>
|
||||
<Card onClick={disabled ? undefined : onClick} rounded={true} fullWidth>
|
||||
<StyledCardContentContainer>
|
||||
<CardContent>
|
||||
<StyledHeader>
|
||||
<StyledIconContainer>{Icon}</StyledIconContainer>
|
||||
<StyledTitle disabled={disabled}>
|
||||
{title}
|
||||
{soon && <Pill label={t`Soon`} />}
|
||||
</StyledTitle>
|
||||
{Status && Status}
|
||||
<StyledIconChevronRightContainer>
|
||||
<IconChevronRight size={theme.icon.size.sm} />
|
||||
</StyledIconChevronRightContainer>
|
||||
</StyledHeader>
|
||||
{description && (
|
||||
<StyledDescription>{description}</StyledDescription>
|
||||
)}
|
||||
</CardContent>
|
||||
</StyledCardContentContainer>
|
||||
</Card>
|
||||
</StyledCardWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
+23
-17
@@ -14,10 +14,12 @@ const StyledNameCell = styled.div`
|
||||
gap: ${themeCssVariables.spacing[1]};
|
||||
`;
|
||||
|
||||
const StyledTableRow = styled(TableRow)`
|
||||
&:hover {
|
||||
background: ${themeCssVariables.background.transparent.light};
|
||||
cursor: pointer;
|
||||
const StyledTableRowContainer = styled.div`
|
||||
> * {
|
||||
&:hover {
|
||||
background-color: ${themeCssVariables.background.transparent.light};
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -32,19 +34,23 @@ export const SettingsConnectedAccountsTableRow = ({
|
||||
const IconComponent = SettingsConnectedAccountIcon({ account });
|
||||
|
||||
return (
|
||||
<StyledTableRow key={account.id} gridAutoColumns="332px 1fr">
|
||||
<TableCell>
|
||||
<StyledNameCell>
|
||||
<IconComponent
|
||||
size={theme.icon.size.md}
|
||||
stroke={theme.icon.stroke.sm}
|
||||
<StyledTableRowContainer>
|
||||
<TableRow key={account.id} gridAutoColumns="332px 1fr">
|
||||
<TableCell>
|
||||
<StyledNameCell>
|
||||
<IconComponent
|
||||
size={theme.icon.size.md}
|
||||
stroke={theme.icon.stroke.sm}
|
||||
/>
|
||||
{account.handle}
|
||||
</StyledNameCell>
|
||||
</TableCell>
|
||||
<TableCell align="right">
|
||||
<SettingsAccountsConnectedAccountsRowRightContainer
|
||||
account={account}
|
||||
/>
|
||||
{account.handle}
|
||||
</StyledNameCell>
|
||||
</TableCell>
|
||||
<TableCell align="right">
|
||||
<SettingsAccountsConnectedAccountsRowRightContainer account={account} />
|
||||
</TableCell>
|
||||
</StyledTableRow>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</StyledTableRowContainer>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -25,9 +25,10 @@ const StyledCounterContainer = styled.div<{ showButtons: boolean }>`
|
||||
: themeCssVariables.spacing[16]};
|
||||
`;
|
||||
|
||||
const StyledTextInput = styled(SettingsTextInput)`
|
||||
const StyledTextInputContainer = styled.div`
|
||||
width: ${themeCssVariables.spacing[16]};
|
||||
input {
|
||||
|
||||
> * input {
|
||||
width: ${themeCssVariables.spacing[16]};
|
||||
height: ${themeCssVariables.spacing[6]};
|
||||
text-align: center;
|
||||
@@ -84,14 +85,16 @@ export const SettingsCounter = ({
|
||||
disabled={disabled}
|
||||
/>
|
||||
)}
|
||||
<StyledTextInput
|
||||
instanceId="settings-counter-input"
|
||||
name="counter"
|
||||
fullWidth
|
||||
value={value.toString()}
|
||||
onChange={handleTextInputChange}
|
||||
disabled={disabled}
|
||||
/>
|
||||
<StyledTextInputContainer>
|
||||
<SettingsTextInput
|
||||
instanceId="settings-counter-input"
|
||||
name="counter"
|
||||
fullWidth
|
||||
value={value.toString()}
|
||||
onChange={handleTextInputChange}
|
||||
disabled={disabled}
|
||||
/>
|
||||
</StyledTextInputContainer>
|
||||
{showButtons && (
|
||||
<IconButton
|
||||
size="small"
|
||||
|
||||
@@ -28,15 +28,16 @@ type SettingsDnsRecordsTableProps = {
|
||||
records: DnsRecord[];
|
||||
};
|
||||
|
||||
const StyledTableRow = styled(TableRow)`
|
||||
& > * {
|
||||
const StyledTableRowContainer = styled.div`
|
||||
> * > * {
|
||||
min-width: 0;
|
||||
max-width: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledTableCell = styled(TableCell)`
|
||||
const StyledTableCellFontWrapper = styled.div`
|
||||
display: contents;
|
||||
font-family: monospace;
|
||||
`;
|
||||
|
||||
@@ -69,53 +70,69 @@ export const SettingsDnsRecordsTable = ({
|
||||
|
||||
return (
|
||||
<Table>
|
||||
<StyledTableRow gridAutoColumns={gridAutoColumns}>
|
||||
<TableHeader align="center">{t`Type`}</TableHeader>
|
||||
<TableHeader align="center">{t`Key`}</TableHeader>
|
||||
<TableHeader align="center">{t`Value`}</TableHeader>
|
||||
{hasPriorityRecords && (
|
||||
<TableHeader align="center">{t`Priority`}</TableHeader>
|
||||
)}
|
||||
{hasTtlRecords && <TableHeader align="center">{t`TTL`}</TableHeader>}
|
||||
{hasStatusRecords && (
|
||||
<TableHeader align="center">{t`Status`}</TableHeader>
|
||||
)}
|
||||
</StyledTableRow>
|
||||
<StyledTableRowContainer>
|
||||
<TableRow gridAutoColumns={gridAutoColumns}>
|
||||
<TableHeader align="center">{t`Type`}</TableHeader>
|
||||
<TableHeader align="center">{t`Key`}</TableHeader>
|
||||
<TableHeader align="center">{t`Value`}</TableHeader>
|
||||
{hasPriorityRecords && (
|
||||
<TableHeader align="center">{t`Priority`}</TableHeader>
|
||||
)}
|
||||
{hasTtlRecords && <TableHeader align="center">{t`TTL`}</TableHeader>}
|
||||
{hasStatusRecords && (
|
||||
<TableHeader align="center">{t`Status`}</TableHeader>
|
||||
)}
|
||||
</TableRow>
|
||||
</StyledTableRowContainer>
|
||||
|
||||
{records.map((record) => (
|
||||
<StyledTableRow key={record.value} gridAutoColumns={gridAutoColumns}>
|
||||
<TableCell>{record.type}</TableCell>
|
||||
<StyledTableCell
|
||||
onClick={() => {
|
||||
copyToClipboard(record.key || '');
|
||||
}}
|
||||
>
|
||||
<OverflowingTextWithTooltip text={record.key} />
|
||||
</StyledTableCell>
|
||||
<StyledTableRowContainer key={record.value}>
|
||||
<TableRow gridAutoColumns={gridAutoColumns}>
|
||||
<TableCell>{record.type}</TableCell>
|
||||
<StyledTableCellFontWrapper>
|
||||
<TableCell
|
||||
onClick={() => {
|
||||
copyToClipboard(record.key || '');
|
||||
}}
|
||||
>
|
||||
<OverflowingTextWithTooltip text={record.key} />
|
||||
</TableCell>
|
||||
</StyledTableCellFontWrapper>
|
||||
|
||||
<StyledTableCell
|
||||
onClick={() => {
|
||||
copyToClipboard(record.value);
|
||||
}}
|
||||
>
|
||||
<OverflowingTextWithTooltip text={record.value} />
|
||||
</StyledTableCell>
|
||||
<StyledTableCellFontWrapper>
|
||||
<TableCell
|
||||
onClick={() => {
|
||||
copyToClipboard(record.value);
|
||||
}}
|
||||
>
|
||||
<OverflowingTextWithTooltip text={record.value} />
|
||||
</TableCell>
|
||||
</StyledTableCellFontWrapper>
|
||||
|
||||
{hasPriorityRecords && (
|
||||
<StyledTableCell>{record.priority}</StyledTableCell>
|
||||
)}
|
||||
{hasTtlRecords && <StyledTableCell>{record.ttl}</StyledTableCell>}
|
||||
{hasStatusRecords && (
|
||||
<StyledTableCell>
|
||||
{'status' in record ? (
|
||||
<Status
|
||||
color={record.statusColor}
|
||||
text={capitalize(record.status)}
|
||||
/>
|
||||
) : null}
|
||||
</StyledTableCell>
|
||||
)}
|
||||
</StyledTableRow>
|
||||
{hasPriorityRecords && (
|
||||
<StyledTableCellFontWrapper>
|
||||
<TableCell>{record.priority}</TableCell>
|
||||
</StyledTableCellFontWrapper>
|
||||
)}
|
||||
{hasTtlRecords && (
|
||||
<StyledTableCellFontWrapper>
|
||||
<TableCell>{record.ttl}</TableCell>
|
||||
</StyledTableCellFontWrapper>
|
||||
)}
|
||||
{hasStatusRecords && (
|
||||
<StyledTableCellFontWrapper>
|
||||
<TableCell>
|
||||
{'status' in record ? (
|
||||
<Status
|
||||
color={record.statusColor}
|
||||
text={capitalize(record.status)}
|
||||
/>
|
||||
) : null}
|
||||
</TableCell>
|
||||
</StyledTableCellFontWrapper>
|
||||
)}
|
||||
</TableRow>
|
||||
</StyledTableRowContainer>
|
||||
))}
|
||||
</Table>
|
||||
);
|
||||
|
||||
@@ -8,10 +8,12 @@ import { Card, CardFooter } from 'twenty-ui/layout';
|
||||
import { ThemeContext, themeCssVariables } from 'twenty-ui/theme-constants';
|
||||
import { SettingsListItemCardContent } from './SettingsListItemCardContent';
|
||||
|
||||
const StyledFooter = styled(CardFooter)`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
padding: ${themeCssVariables.spacing[1]};
|
||||
const StyledFooterContainer = styled.div`
|
||||
> * {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
padding: ${themeCssVariables.spacing[1]};
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledButton = styled.button`
|
||||
@@ -91,12 +93,14 @@ export const SettingsListCard = <
|
||||
/>
|
||||
))}
|
||||
{hasFooter && (
|
||||
<StyledFooter divider={!!items.length}>
|
||||
<StyledButton onClick={onFooterButtonClick}>
|
||||
<IconPlus size={theme.icon.size.md} />
|
||||
{footerButtonLabel}
|
||||
</StyledButton>
|
||||
</StyledFooter>
|
||||
<StyledFooterContainer>
|
||||
<CardFooter divider={!!items.length}>
|
||||
<StyledButton onClick={onFooterButtonClick}>
|
||||
<IconPlus size={theme.icon.size.md} />
|
||||
{footerButtonLabel}
|
||||
</StyledButton>
|
||||
</CardFooter>
|
||||
</StyledFooterContainer>
|
||||
)}
|
||||
</Card>
|
||||
);
|
||||
|
||||
+48
-36
@@ -6,15 +6,17 @@ import { IconChevronRight, type IconComponent } from 'twenty-ui/display';
|
||||
import { CardContent } from 'twenty-ui/layout';
|
||||
import { ThemeContext, themeCssVariables } from 'twenty-ui/theme-constants';
|
||||
|
||||
const StyledRow = styled(CardContent)`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
font-size: ${themeCssVariables.font.size.sm};
|
||||
font-weight: ${themeCssVariables.font.weight.medium};
|
||||
gap: ${themeCssVariables.spacing[2]};
|
||||
padding: ${themeCssVariables.spacing[2]};
|
||||
padding-left: ${themeCssVariables.spacing[3]};
|
||||
min-height: ${themeCssVariables.spacing[6]};
|
||||
const StyledRowContainer = styled.div`
|
||||
> * {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
font-size: ${themeCssVariables.font.size.sm};
|
||||
font-weight: ${themeCssVariables.font.weight.medium};
|
||||
gap: ${themeCssVariables.spacing[2]};
|
||||
padding: ${themeCssVariables.spacing[2]};
|
||||
padding-left: ${themeCssVariables.spacing[3]};
|
||||
min-height: ${themeCssVariables.spacing[6]};
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledRightContainer = styled.div`
|
||||
@@ -43,9 +45,11 @@ const StyledDescription = styled.span`
|
||||
padding-left: ${themeCssVariables.spacing[1]};
|
||||
`;
|
||||
|
||||
const StyledLink = styled(Link)`
|
||||
color: ${themeCssVariables.font.color.secondary};
|
||||
text-decoration: none;
|
||||
const StyledLinkContainer = styled.div`
|
||||
> a {
|
||||
color: ${themeCssVariables.font.color.secondary};
|
||||
text-decoration: none;
|
||||
}
|
||||
`;
|
||||
|
||||
type SettingsListItemCardContentProps = {
|
||||
@@ -72,36 +76,44 @@ export const SettingsListItemCardContent = ({
|
||||
const { theme } = useContext(ThemeContext);
|
||||
|
||||
const content = (
|
||||
<StyledRow
|
||||
onClick={onClick}
|
||||
divider={divider}
|
||||
isClickable={!!onClick || !!to}
|
||||
hasHoverHighlight={!!to}
|
||||
>
|
||||
{!!LeftIcon && (
|
||||
<LeftIcon
|
||||
size={theme.icon.size.md}
|
||||
color={LeftIconColor ?? 'currentColor'}
|
||||
/>
|
||||
)}
|
||||
<StyledContent>
|
||||
<StyledLabel>{label}</StyledLabel>
|
||||
{!!description && <StyledDescription>{description}</StyledDescription>}
|
||||
</StyledContent>
|
||||
<StyledRightContainer>
|
||||
{rightComponent}
|
||||
{!!to && (
|
||||
<IconChevronRight
|
||||
<StyledRowContainer>
|
||||
<CardContent
|
||||
onClick={onClick}
|
||||
divider={divider}
|
||||
isClickable={!!onClick || !!to}
|
||||
hasHoverHighlight={!!to}
|
||||
>
|
||||
{!!LeftIcon && (
|
||||
<LeftIcon
|
||||
size={theme.icon.size.md}
|
||||
color={theme.font.color.tertiary}
|
||||
color={LeftIconColor ?? 'currentColor'}
|
||||
/>
|
||||
)}
|
||||
</StyledRightContainer>
|
||||
</StyledRow>
|
||||
<StyledContent>
|
||||
<StyledLabel>{label}</StyledLabel>
|
||||
{!!description && (
|
||||
<StyledDescription>{description}</StyledDescription>
|
||||
)}
|
||||
</StyledContent>
|
||||
<StyledRightContainer>
|
||||
{rightComponent}
|
||||
{!!to && (
|
||||
<IconChevronRight
|
||||
size={theme.icon.size.md}
|
||||
color={theme.font.color.tertiary}
|
||||
/>
|
||||
)}
|
||||
</StyledRightContainer>
|
||||
</CardContent>
|
||||
</StyledRowContainer>
|
||||
);
|
||||
|
||||
if (isDefined(to)) {
|
||||
return <StyledLink to={to}>{content}</StyledLink>;
|
||||
return (
|
||||
<StyledLinkContainer>
|
||||
<Link to={to}>{content}</Link>
|
||||
</StyledLinkContainer>
|
||||
);
|
||||
}
|
||||
|
||||
return content;
|
||||
|
||||
@@ -2,9 +2,17 @@ import { styled } from '@linaria/react';
|
||||
import { Card } from 'twenty-ui/layout';
|
||||
import { themeCssVariables } from 'twenty-ui/theme-constants';
|
||||
|
||||
const StyledCard = styled(Card)`
|
||||
background-color: ${themeCssVariables.background.secondary};
|
||||
const StyledCardContainer = styled.div`
|
||||
height: 40px;
|
||||
|
||||
> * {
|
||||
background-color: ${themeCssVariables.background.secondary};
|
||||
height: 100%;
|
||||
}
|
||||
`;
|
||||
|
||||
export { StyledCard as SettingsListSkeletonCard };
|
||||
export const SettingsListSkeletonCard = () => (
|
||||
<StyledCardContainer>
|
||||
<Card />
|
||||
</StyledCardContainer>
|
||||
);
|
||||
|
||||
+20
-12
@@ -1,6 +1,5 @@
|
||||
import { Separator } from '@/settings/components/Separator';
|
||||
import {
|
||||
StyledSettingsCardContent,
|
||||
StyledSettingsCardDescription,
|
||||
StyledSettingsCardIcon,
|
||||
StyledSettingsCardTextContainer,
|
||||
@@ -16,7 +15,12 @@ import {
|
||||
import { Toggle } from 'twenty-ui/input';
|
||||
import { ThemeContext, themeCssVariables } from 'twenty-ui/theme-constants';
|
||||
|
||||
const StyledSettingsCardToggleContent = styled(StyledSettingsCardContent)`
|
||||
const StyledSettingsCardToggleContent = styled.div<{ disabled?: boolean }>`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
gap: ${themeCssVariables.spacing[3]};
|
||||
background-color: ${themeCssVariables.background.secondary};
|
||||
padding: ${themeCssVariables.spacing[4]};
|
||||
cursor: ${({ disabled }) => (disabled ? 'default' : 'pointer')};
|
||||
position: relative;
|
||||
pointer-events: ${({ disabled }) => (disabled ? 'none' : 'auto')};
|
||||
@@ -26,7 +30,9 @@ const StyledSettingsCardToggleContent = styled(StyledSettingsCardContent)`
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledSettingsCardToggleButton = styled(Toggle)`
|
||||
const StyledSettingsCardToggleButtonContainer = styled.span`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-shrink: 0;
|
||||
margin-left: auto;
|
||||
`;
|
||||
@@ -84,15 +90,17 @@ export const SettingsOptionCardContentToggle = ({
|
||||
</StyledSettingsCardDescription>
|
||||
)}
|
||||
</StyledSettingsCardTextContainer>
|
||||
<StyledSettingsCardToggleButton
|
||||
id={toggleId}
|
||||
value={checked}
|
||||
onChange={onChange}
|
||||
disabled={disabled}
|
||||
toggleSize="small"
|
||||
color={advancedMode ? theme.color.yellow : theme.color.blue}
|
||||
centered={toggleCentered}
|
||||
/>
|
||||
<StyledSettingsCardToggleButtonContainer>
|
||||
<Toggle
|
||||
id={toggleId}
|
||||
value={checked}
|
||||
onChange={onChange}
|
||||
disabled={disabled}
|
||||
toggleSize="small"
|
||||
color={advancedMode ? theme.color.yellow : theme.color.blue}
|
||||
centered={toggleCentered}
|
||||
/>
|
||||
</StyledSettingsCardToggleButtonContainer>
|
||||
</StyledSettingsCardToggleContent>
|
||||
{divider && <Separator />}
|
||||
</>
|
||||
|
||||
@@ -5,22 +5,26 @@ import { type IconComponent } from 'twenty-ui/display';
|
||||
import { Radio } from 'twenty-ui/input';
|
||||
import { ThemeContext, themeCssVariables } from 'twenty-ui/theme-constants';
|
||||
|
||||
const StyledRadioCardContent = styled(CardContent)`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: ${themeCssVariables.spacing[2]};
|
||||
border: 1px solid ${themeCssVariables.border.color.medium};
|
||||
border-radius: ${themeCssVariables.border.radius.sm};
|
||||
flex-grow: 1;
|
||||
gap: ${themeCssVariables.spacing[2]};
|
||||
cursor: pointer;
|
||||
const StyledRadioCardContentContainer = styled.div`
|
||||
> * {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: ${themeCssVariables.spacing[2]};
|
||||
border: 1px solid ${themeCssVariables.border.color.medium};
|
||||
border-radius: ${themeCssVariables.border.radius.sm};
|
||||
flex-grow: 1;
|
||||
gap: ${themeCssVariables.spacing[2]};
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
background: ${themeCssVariables.background.transparent.lighter};
|
||||
&:hover {
|
||||
background: ${themeCssVariables.background.transparent.lighter};
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledRadio = styled(Radio)`
|
||||
const StyledRadioContainer = styled.span`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
margin-left: auto;
|
||||
padding: ${themeCssVariables.spacing[1]};
|
||||
`;
|
||||
@@ -58,13 +62,17 @@ export const SettingsRadioCard = ({
|
||||
const onClick = () => handleSelect(value);
|
||||
|
||||
return (
|
||||
<StyledRadioCardContent tabIndex={0} onClick={onClick}>
|
||||
{Icon && <Icon size={theme.icon.size.xl} color={theme.color.gray10} />}
|
||||
<span>
|
||||
{title && <StyledTitle>{title}</StyledTitle>}
|
||||
{description && <StyledDescription>{description}</StyledDescription>}
|
||||
</span>
|
||||
<StyledRadio value={value} checked={isSelected} />
|
||||
</StyledRadioCardContent>
|
||||
<StyledRadioCardContentContainer>
|
||||
<CardContent tabIndex={0} onClick={onClick}>
|
||||
{Icon && <Icon size={theme.icon.size.xl} color={theme.color.gray10} />}
|
||||
<span>
|
||||
{title && <StyledTitle>{title}</StyledTitle>}
|
||||
{description && <StyledDescription>{description}</StyledDescription>}
|
||||
</span>
|
||||
<StyledRadioContainer>
|
||||
<Radio value={value} checked={isSelected} />
|
||||
</StyledRadioContainer>
|
||||
</CardContent>
|
||||
</StyledRadioCardContentContainer>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -8,12 +8,14 @@ type SettingsSummaryCardProps = {
|
||||
rightComponent: ReactNode;
|
||||
};
|
||||
|
||||
const StyledCardContent = styled(CardContent)`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
gap: ${themeCssVariables.spacing[2]};
|
||||
padding: ${themeCssVariables.spacing[2]};
|
||||
min-height: ${themeCssVariables.spacing[6]};
|
||||
const StyledCardContentContainer = styled.div`
|
||||
> * {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
gap: ${themeCssVariables.spacing[2]};
|
||||
padding: ${themeCssVariables.spacing[2]};
|
||||
min-height: ${themeCssVariables.spacing[6]};
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledTitle = styled.div`
|
||||
@@ -29,9 +31,11 @@ export const SettingsSummaryCard = ({
|
||||
rightComponent,
|
||||
}: SettingsSummaryCardProps) => (
|
||||
<Card>
|
||||
<StyledCardContent>
|
||||
<StyledTitle>{title}</StyledTitle>
|
||||
{rightComponent}
|
||||
</StyledCardContent>
|
||||
<StyledCardContentContainer>
|
||||
<CardContent>
|
||||
<StyledTitle>{title}</StyledTitle>
|
||||
{rightComponent}
|
||||
</CardContent>
|
||||
</StyledCardContentContainer>
|
||||
</Card>
|
||||
);
|
||||
|
||||
+17
-7
@@ -28,8 +28,10 @@ const StyledButtonContainer = styled.div`
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
const StyledDownChevron = styled(IconChevronDown)`
|
||||
const StyledDownChevronContainer = styled.span`
|
||||
align-items: center;
|
||||
color: ${themeCssVariables.font.color.primary};
|
||||
display: flex;
|
||||
position: absolute;
|
||||
right: ${themeCssVariables.spacing['1.5']};
|
||||
top: 50%;
|
||||
@@ -45,9 +47,11 @@ const StyledSpan = styled.span`
|
||||
margin-left: ${themeCssVariables.spacing[2]};
|
||||
`;
|
||||
|
||||
const StyledButton = styled(Button)`
|
||||
color: ${themeCssVariables.font.color.primary};
|
||||
padding-right: ${themeCssVariables.spacing[6]};
|
||||
const StyledButtonWrapper = styled.div`
|
||||
> button {
|
||||
color: ${themeCssVariables.font.color.primary};
|
||||
padding-right: ${themeCssVariables.spacing[6]};
|
||||
}
|
||||
`;
|
||||
|
||||
export const SettingsDataModelNewFieldBreadcrumbDropDown = () => {
|
||||
@@ -87,11 +91,17 @@ export const SettingsDataModelNewFieldBreadcrumbDropDown = () => {
|
||||
dropdownId={dropdownId}
|
||||
clickableComponent={
|
||||
<StyledButtonContainer>
|
||||
<StyledDownChevron size={theme.icon.size.md} />
|
||||
<StyledDownChevronContainer>
|
||||
<IconChevronDown size={theme.icon.size.md} />
|
||||
</StyledDownChevronContainer>
|
||||
{isConfigureStep ? (
|
||||
<StyledButton variant="tertiary" title={t`2. Configure`} />
|
||||
<StyledButtonWrapper>
|
||||
<Button variant="tertiary" title={t`2. Configure`} />
|
||||
</StyledButtonWrapper>
|
||||
) : (
|
||||
<StyledButton variant="tertiary" title={t`1. Type`} />
|
||||
<StyledButtonWrapper>
|
||||
<Button variant="tertiary" title={t`1. Type`} />
|
||||
</StyledButtonWrapper>
|
||||
)}
|
||||
</StyledButtonContainer>
|
||||
}
|
||||
|
||||
+21
-11
@@ -13,12 +13,16 @@ type SettingsDataModelPreviewFormCardProps = {
|
||||
disabled?: boolean;
|
||||
};
|
||||
|
||||
const StyledPreviewContainer = styled(CardContent)`
|
||||
background-color: ${themeCssVariables.background.transparent.lighter};
|
||||
const StyledPreviewContainerWrapper = styled.div`
|
||||
> * {
|
||||
background-color: ${themeCssVariables.background.transparent.lighter};
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledFormContainer = styled(CardContent)`
|
||||
padding: 0;
|
||||
const StyledFormContainerWrapper = styled.div`
|
||||
> * {
|
||||
padding: 0;
|
||||
}
|
||||
`;
|
||||
|
||||
export const SettingsDataModelPreviewFormCard = ({
|
||||
@@ -27,12 +31,18 @@ export const SettingsDataModelPreviewFormCard = ({
|
||||
form,
|
||||
}: SettingsDataModelPreviewFormCardProps) => (
|
||||
<Card className={className} fullWidth rounded>
|
||||
<StyledPreviewContainer divider={!!form}>
|
||||
<StyledFormCardTitle>
|
||||
<Trans>Preview</Trans>
|
||||
</StyledFormCardTitle>
|
||||
{preview}
|
||||
</StyledPreviewContainer>
|
||||
{!!form && <StyledFormContainer>{form}</StyledFormContainer>}
|
||||
<StyledPreviewContainerWrapper>
|
||||
<CardContent divider={!!form}>
|
||||
<StyledFormCardTitle>
|
||||
<Trans>Preview</Trans>
|
||||
</StyledFormCardTitle>
|
||||
{preview}
|
||||
</CardContent>
|
||||
</StyledPreviewContainerWrapper>
|
||||
{!!form && (
|
||||
<StyledFormContainerWrapper>
|
||||
<CardContent>{form}</CardContent>
|
||||
</StyledFormContainerWrapper>
|
||||
)}
|
||||
</Card>
|
||||
);
|
||||
|
||||
+10
-8
@@ -56,7 +56,7 @@ const StyledCardContainer = styled.div`
|
||||
width: calc(50% - ${themeCssVariables.spacing[1]});
|
||||
`;
|
||||
|
||||
const StyledSearchInput = styled(SettingsTextInput)`
|
||||
const StyledSearchInputContainer = styled.div`
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
@@ -108,13 +108,15 @@ export const SettingsObjectNewFieldSelector = ({
|
||||
<>
|
||||
{' '}
|
||||
<Section>
|
||||
<StyledSearchInput
|
||||
instanceId="new-field-type-search"
|
||||
LeftIcon={IconSearch}
|
||||
placeholder={t`Search a type`}
|
||||
value={searchQuery}
|
||||
onChange={setSearchQuery}
|
||||
/>
|
||||
<StyledSearchInputContainer>
|
||||
<SettingsTextInput
|
||||
instanceId="new-field-type-search"
|
||||
LeftIcon={IconSearch}
|
||||
placeholder={t`Search a type`}
|
||||
value={searchQuery}
|
||||
onChange={setSearchQuery}
|
||||
/>
|
||||
</StyledSearchInputContainer>
|
||||
</Section>
|
||||
<Controller
|
||||
name="type"
|
||||
|
||||
+11
-9
@@ -33,7 +33,7 @@ export const settingsDataModelFieldDateFormSchema = z.object({
|
||||
settings: fieldDateSettings.optional(),
|
||||
});
|
||||
|
||||
const StyledTextInput = styled(SettingsTextInput)`
|
||||
const StyledTextInputContainer = styled.div`
|
||||
padding: ${themeCssVariables.spacing[4]};
|
||||
padding-top: 0;
|
||||
`;
|
||||
@@ -117,14 +117,16 @@ export const SettingsDataModelFieldDateForm = ({
|
||||
control={control}
|
||||
defaultValue={initialCustomUnicodeDateFormat}
|
||||
render={({ field: { onChange, value } }) => (
|
||||
<StyledTextInput
|
||||
instanceId="custom-date-format-input"
|
||||
placeholder={t`Format e.g. d-MMM-y (qqq''yy)`}
|
||||
value={value}
|
||||
onChange={(value) => onChange(value)}
|
||||
disabled={false}
|
||||
fullWidth
|
||||
/>
|
||||
<StyledTextInputContainer>
|
||||
<SettingsTextInput
|
||||
instanceId="custom-date-format-input"
|
||||
placeholder={t`Format e.g. d-MMM-y (qqq''yy)`}
|
||||
value={value}
|
||||
onChange={(value) => onChange(value)}
|
||||
disabled={false}
|
||||
fullWidth
|
||||
/>
|
||||
</StyledTextInputContainer>
|
||||
)}
|
||||
/>
|
||||
</AnimatedExpandableContainer>
|
||||
|
||||
+220
-199
@@ -67,8 +67,10 @@ type SettingsDataModelFieldSelectFormProps = {
|
||||
disabled?: boolean;
|
||||
};
|
||||
|
||||
const StyledContainer = styled(CardContent)`
|
||||
padding-bottom: 14px;
|
||||
const StyledContainerWrapper = styled.div`
|
||||
> * {
|
||||
padding-bottom: 14px;
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledOptionsLabel = styled.div<{
|
||||
@@ -119,18 +121,24 @@ const StyledIconContainer = styled.div`
|
||||
margin-top: ${themeCssVariables.spacing[1]};
|
||||
`;
|
||||
|
||||
const StyledIconPoint = styled(IconPoint)`
|
||||
const StyledIconPointContainer = styled.span`
|
||||
margin-right: ${themeCssVariables.spacing['0.5']};
|
||||
display: flex;
|
||||
align-items: center;
|
||||
`;
|
||||
|
||||
const StyledFooter = styled(CardFooter)`
|
||||
background-color: ${themeCssVariables.background.secondary};
|
||||
padding: ${themeCssVariables.spacing[1]};
|
||||
const StyledFooterContainer = styled.div`
|
||||
> * {
|
||||
background-color: ${themeCssVariables.background.secondary};
|
||||
padding: ${themeCssVariables.spacing[1]};
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledButton = styled(LightButton)`
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
const StyledButtonContainer = styled.div`
|
||||
> button {
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledOptionsHeaderContainer = styled.div`
|
||||
@@ -316,199 +324,212 @@ export const SettingsDataModelFieldSelectForm = ({
|
||||
defaultValue={initialOptions}
|
||||
render={({ field: { onChange, value: options } }) => (
|
||||
<>
|
||||
<StyledContainer>
|
||||
<StyledOptionsHeaderContainer>
|
||||
<StyledLabelContainer>
|
||||
{!isBulkInputMode && (
|
||||
<AdvancedSettingsWrapper animationDimension="width" hideDot>
|
||||
<StyledApiKeyContainer>
|
||||
<StyledIconContainer>
|
||||
<StyledIconPoint
|
||||
size={12}
|
||||
color={theme.color.yellow}
|
||||
fill={theme.color.yellow}
|
||||
/>
|
||||
</StyledIconContainer>
|
||||
<StyledApiKey>{t`API values`}</StyledApiKey>
|
||||
</StyledApiKeyContainer>
|
||||
</AdvancedSettingsWrapper>
|
||||
)}
|
||||
<StyledOptionsLabel
|
||||
isAdvancedModeEnabled={isAdvancedModeEnabled}
|
||||
isBulkInputMode={isBulkInputMode}
|
||||
>
|
||||
{t`Options`}
|
||||
</StyledOptionsLabel>
|
||||
</StyledLabelContainer>
|
||||
{!disabled && (
|
||||
<Dropdown
|
||||
dropdownId={OPTIONS_DROPDOWN_ID}
|
||||
clickableComponent={
|
||||
<LightIconButton
|
||||
Icon={IconDotsVertical}
|
||||
accent="tertiary"
|
||||
/>
|
||||
}
|
||||
dropdownComponents={
|
||||
<DropdownContent
|
||||
widthInPixels={GenericDropdownContentWidth.Narrow}
|
||||
<StyledContainerWrapper>
|
||||
<CardContent>
|
||||
<StyledOptionsHeaderContainer>
|
||||
<StyledLabelContainer>
|
||||
{!isBulkInputMode && (
|
||||
<AdvancedSettingsWrapper
|
||||
animationDimension="width"
|
||||
hideDot
|
||||
>
|
||||
<DropdownMenuItemsContainer>
|
||||
<MenuItem
|
||||
text={
|
||||
isBulkInputMode ? t`Single edit` : t`Bulk edit`
|
||||
}
|
||||
LeftIcon={IconPencil}
|
||||
onClick={() => {
|
||||
if (!isBulkInputMode) {
|
||||
setBulkInputText(
|
||||
convertOptionsToBulkText(options),
|
||||
);
|
||||
}
|
||||
setIsBulkInputMode(
|
||||
(currentInputMode) => !currentInputMode,
|
||||
);
|
||||
closeOptionsDropdown(OPTIONS_DROPDOWN_ID);
|
||||
}}
|
||||
/>
|
||||
<MenuItem
|
||||
text={t`Remove all`}
|
||||
accent="danger"
|
||||
LeftIcon={IconTrash}
|
||||
onClick={() => {
|
||||
onChange([]);
|
||||
closeOptionsDropdown(OPTIONS_DROPDOWN_ID);
|
||||
}}
|
||||
/>
|
||||
</DropdownMenuItemsContainer>
|
||||
</DropdownContent>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</StyledOptionsHeaderContainer>
|
||||
|
||||
{isBulkInputMode ? (
|
||||
<StyledTextAreaContainer>
|
||||
<TextArea
|
||||
textAreaId="bulk-options-input"
|
||||
placeholder={t`Enter one option per line`}
|
||||
value={bulkInputText}
|
||||
onChange={(nextOptionAsText) => {
|
||||
if (disabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
const nextOptions = convertBulkTextToOptions(
|
||||
nextOptionAsText,
|
||||
options,
|
||||
);
|
||||
|
||||
onChange(nextOptions);
|
||||
setBulkInputText(nextOptionAsText);
|
||||
}}
|
||||
minRows={5}
|
||||
maxRows={15}
|
||||
disabled={disabled}
|
||||
/>
|
||||
<StyledHelpText>
|
||||
{t`Enter one option per line. Each line will become a new option.`}
|
||||
</StyledHelpText>
|
||||
</StyledTextAreaContainer>
|
||||
) : (
|
||||
<>
|
||||
<DraggableList
|
||||
onDragEnd={(result) =>
|
||||
!disabled
|
||||
? handleDragEnd(options, result, onChange)
|
||||
: undefined
|
||||
}
|
||||
draggableItems={
|
||||
<>
|
||||
{options.map((option, index) => (
|
||||
<DraggableItem
|
||||
isInsideScrollableContainer
|
||||
key={option.id}
|
||||
draggableId={option.id}
|
||||
index={index}
|
||||
isDragDisabled={options.length === 1}
|
||||
itemComponent={
|
||||
<SettingsDataModelFieldSelectFormOptionRow
|
||||
key={option.id}
|
||||
option={option}
|
||||
isNewRow={index === options.length - 1}
|
||||
onChange={(nextOption) => {
|
||||
if (disabled) {
|
||||
return;
|
||||
}
|
||||
const nextOptions = toSpliced(
|
||||
options,
|
||||
index,
|
||||
1,
|
||||
nextOption,
|
||||
);
|
||||
onChange(nextOptions);
|
||||
|
||||
// Update option value in defaultValue if value has changed
|
||||
if (
|
||||
nextOption.value !== option.value &&
|
||||
isOptionDefaultValue(option.value)
|
||||
) {
|
||||
handleRemoveOptionAsDefault(option.value);
|
||||
handleSetOptionAsDefault(nextOption.value);
|
||||
}
|
||||
}}
|
||||
onRemove={() => {
|
||||
if (disabled) {
|
||||
return;
|
||||
}
|
||||
const nextOptions = toSpliced(
|
||||
options,
|
||||
index,
|
||||
1,
|
||||
).map((option, nextOptionIndex) => ({
|
||||
...option,
|
||||
position: nextOptionIndex,
|
||||
}));
|
||||
onChange(nextOptions);
|
||||
}}
|
||||
isDefault={isOptionDefaultValue(option.value)}
|
||||
fieldIsNullable={!!isNullable}
|
||||
onSetAsDefault={() => {
|
||||
if (disabled) {
|
||||
return;
|
||||
}
|
||||
handleSetOptionAsDefault(option.value);
|
||||
}}
|
||||
onRemoveAsDefault={() => {
|
||||
if (disabled) {
|
||||
return;
|
||||
}
|
||||
handleRemoveOptionAsDefault(option.value);
|
||||
}}
|
||||
onInputEnter={() => {
|
||||
if (disabled) {
|
||||
return;
|
||||
}
|
||||
handleInputEnter();
|
||||
}}
|
||||
<StyledApiKeyContainer>
|
||||
<StyledIconContainer>
|
||||
<StyledIconPointContainer>
|
||||
<IconPoint
|
||||
size={12}
|
||||
color={theme.color.yellow}
|
||||
fill={theme.color.yellow}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</StyledContainer>
|
||||
</StyledIconPointContainer>
|
||||
</StyledIconContainer>
|
||||
<StyledApiKey>{t`API values`}</StyledApiKey>
|
||||
</StyledApiKeyContainer>
|
||||
</AdvancedSettingsWrapper>
|
||||
)}
|
||||
<StyledOptionsLabel
|
||||
isAdvancedModeEnabled={isAdvancedModeEnabled}
|
||||
isBulkInputMode={isBulkInputMode}
|
||||
>
|
||||
{t`Options`}
|
||||
</StyledOptionsLabel>
|
||||
</StyledLabelContainer>
|
||||
{!disabled && (
|
||||
<Dropdown
|
||||
dropdownId={OPTIONS_DROPDOWN_ID}
|
||||
clickableComponent={
|
||||
<LightIconButton
|
||||
Icon={IconDotsVertical}
|
||||
accent="tertiary"
|
||||
/>
|
||||
}
|
||||
dropdownComponents={
|
||||
<DropdownContent
|
||||
widthInPixels={GenericDropdownContentWidth.Narrow}
|
||||
>
|
||||
<DropdownMenuItemsContainer>
|
||||
<MenuItem
|
||||
text={
|
||||
isBulkInputMode ? t`Single edit` : t`Bulk edit`
|
||||
}
|
||||
LeftIcon={IconPencil}
|
||||
onClick={() => {
|
||||
if (!isBulkInputMode) {
|
||||
setBulkInputText(
|
||||
convertOptionsToBulkText(options),
|
||||
);
|
||||
}
|
||||
setIsBulkInputMode(
|
||||
(currentInputMode) => !currentInputMode,
|
||||
);
|
||||
closeOptionsDropdown(OPTIONS_DROPDOWN_ID);
|
||||
}}
|
||||
/>
|
||||
<MenuItem
|
||||
text={t`Remove all`}
|
||||
accent="danger"
|
||||
LeftIcon={IconTrash}
|
||||
onClick={() => {
|
||||
onChange([]);
|
||||
closeOptionsDropdown(OPTIONS_DROPDOWN_ID);
|
||||
}}
|
||||
/>
|
||||
</DropdownMenuItemsContainer>
|
||||
</DropdownContent>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</StyledOptionsHeaderContainer>
|
||||
|
||||
{isBulkInputMode ? (
|
||||
<StyledTextAreaContainer>
|
||||
<TextArea
|
||||
textAreaId="bulk-options-input"
|
||||
placeholder={t`Enter one option per line`}
|
||||
value={bulkInputText}
|
||||
onChange={(nextOptionAsText) => {
|
||||
if (disabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
const nextOptions = convertBulkTextToOptions(
|
||||
nextOptionAsText,
|
||||
options,
|
||||
);
|
||||
|
||||
onChange(nextOptions);
|
||||
setBulkInputText(nextOptionAsText);
|
||||
}}
|
||||
minRows={5}
|
||||
maxRows={15}
|
||||
disabled={disabled}
|
||||
/>
|
||||
<StyledHelpText>
|
||||
{t`Enter one option per line. Each line will become a new option.`}
|
||||
</StyledHelpText>
|
||||
</StyledTextAreaContainer>
|
||||
) : (
|
||||
<>
|
||||
<DraggableList
|
||||
onDragEnd={(result) =>
|
||||
!disabled
|
||||
? handleDragEnd(options, result, onChange)
|
||||
: undefined
|
||||
}
|
||||
draggableItems={
|
||||
<>
|
||||
{options.map((option, index) => (
|
||||
<DraggableItem
|
||||
isInsideScrollableContainer
|
||||
key={option.id}
|
||||
draggableId={option.id}
|
||||
index={index}
|
||||
isDragDisabled={options.length === 1}
|
||||
itemComponent={
|
||||
<SettingsDataModelFieldSelectFormOptionRow
|
||||
key={option.id}
|
||||
option={option}
|
||||
isNewRow={index === options.length - 1}
|
||||
onChange={(nextOption) => {
|
||||
if (disabled) {
|
||||
return;
|
||||
}
|
||||
const nextOptions = toSpliced(
|
||||
options,
|
||||
index,
|
||||
1,
|
||||
nextOption,
|
||||
);
|
||||
onChange(nextOptions);
|
||||
|
||||
// Update option value in defaultValue if value has changed
|
||||
if (
|
||||
nextOption.value !== option.value &&
|
||||
isOptionDefaultValue(option.value)
|
||||
) {
|
||||
handleRemoveOptionAsDefault(option.value);
|
||||
handleSetOptionAsDefault(
|
||||
nextOption.value,
|
||||
);
|
||||
}
|
||||
}}
|
||||
onRemove={() => {
|
||||
if (disabled) {
|
||||
return;
|
||||
}
|
||||
const nextOptions = toSpliced(
|
||||
options,
|
||||
index,
|
||||
1,
|
||||
).map((option, nextOptionIndex) => ({
|
||||
...option,
|
||||
position: nextOptionIndex,
|
||||
}));
|
||||
onChange(nextOptions);
|
||||
}}
|
||||
isDefault={isOptionDefaultValue(option.value)}
|
||||
fieldIsNullable={!!isNullable}
|
||||
onSetAsDefault={() => {
|
||||
if (disabled) {
|
||||
return;
|
||||
}
|
||||
handleSetOptionAsDefault(option.value);
|
||||
}}
|
||||
onRemoveAsDefault={() => {
|
||||
if (disabled) {
|
||||
return;
|
||||
}
|
||||
handleRemoveOptionAsDefault(option.value);
|
||||
}}
|
||||
onInputEnter={() => {
|
||||
if (disabled) {
|
||||
return;
|
||||
}
|
||||
handleInputEnter();
|
||||
}}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</CardContent>
|
||||
</StyledContainerWrapper>
|
||||
{!disabled && !isBulkInputMode && (
|
||||
<StyledFooter>
|
||||
<StyledButton
|
||||
title={t`Add option`}
|
||||
Icon={IconPlus}
|
||||
onClick={handleAddOption}
|
||||
/>
|
||||
</StyledFooter>
|
||||
<StyledFooterContainer>
|
||||
<CardFooter>
|
||||
<StyledButtonContainer>
|
||||
<LightButton
|
||||
title={t`Add option`}
|
||||
Icon={IconPlus}
|
||||
onClick={handleAddOption}
|
||||
/>
|
||||
</StyledButtonContainer>
|
||||
</CardFooter>
|
||||
</StyledFooterContainer>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
|
||||
+31
-18
@@ -75,13 +75,14 @@ const StyledRow = styled.div`
|
||||
padding: ${themeCssVariables.spacing['1.5']} 0;
|
||||
`;
|
||||
|
||||
const StyledColorSample = styled(ColorSample)`
|
||||
const StyledColorSampleContainer = styled.span`
|
||||
cursor: pointer;
|
||||
margin-top: ${themeCssVariables.spacing[1]};
|
||||
margin-bottom: ${themeCssVariables.spacing[1]};
|
||||
|
||||
margin-right: 14px;
|
||||
margin-left: 14px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
`;
|
||||
|
||||
const StyledOptionInputContainer = styled.div`
|
||||
@@ -93,12 +94,16 @@ const StyledOptionInputContainer = styled.div`
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledIconGripVertical = styled(IconGripVertical)`
|
||||
const StyledIconGripVerticalContainer = styled.span`
|
||||
margin-right: 3px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
`;
|
||||
|
||||
const StyledLightIconButton = styled(LightIconButton)`
|
||||
const StyledLightIconButtonContainer = styled.span`
|
||||
margin-left: ${themeCssVariables.spacing[2]};
|
||||
display: flex;
|
||||
align-items: center;
|
||||
`;
|
||||
|
||||
export const SettingsDataModelFieldSelectFormOptionRow = ({
|
||||
@@ -129,14 +134,16 @@ export const SettingsDataModelFieldSelectFormOptionRow = ({
|
||||
|
||||
return (
|
||||
<StyledRow className={className}>
|
||||
<StyledIconGripVertical
|
||||
style={{
|
||||
minWidth: theme.icon.size.md,
|
||||
}}
|
||||
size={theme.icon.size.md}
|
||||
stroke={theme.icon.stroke.sm}
|
||||
color={theme.font.color.extraLight}
|
||||
/>
|
||||
<StyledIconGripVerticalContainer>
|
||||
<IconGripVertical
|
||||
style={{
|
||||
minWidth: theme.icon.size.md,
|
||||
}}
|
||||
size={theme.icon.size.md}
|
||||
stroke={theme.icon.stroke.sm}
|
||||
color={theme.font.color.extraLight}
|
||||
/>
|
||||
</StyledIconGripVerticalContainer>
|
||||
<AdvancedSettingsWrapper animationDimension="width" hideDot>
|
||||
<StyledOptionInputContainer>
|
||||
<SettingsTextInput
|
||||
@@ -156,7 +163,11 @@ export const SettingsDataModelFieldSelectFormOptionRow = ({
|
||||
<Dropdown
|
||||
dropdownId={SELECT_COLOR_DROPDOWN_ID}
|
||||
dropdownPlacement="bottom-start"
|
||||
clickableComponent={<StyledColorSample colorName={option.color} />}
|
||||
clickableComponent={
|
||||
<StyledColorSampleContainer>
|
||||
<ColorSample colorName={option.color} />
|
||||
</StyledColorSampleContainer>
|
||||
}
|
||||
dropdownComponents={
|
||||
<DropdownContent>
|
||||
<DropdownMenuItemsContainer>
|
||||
@@ -203,11 +214,13 @@ export const SettingsDataModelFieldSelectFormOptionRow = ({
|
||||
dropdownId={SELECT_ACTIONS_DROPDOWN_ID}
|
||||
dropdownPlacement="right-start"
|
||||
clickableComponent={
|
||||
<StyledLightIconButton
|
||||
accent="tertiary"
|
||||
Icon={IconDotsVertical}
|
||||
disabled={shouldForbidRemoveAsDefault}
|
||||
/>
|
||||
<StyledLightIconButtonContainer>
|
||||
<LightIconButton
|
||||
accent="tertiary"
|
||||
Icon={IconDotsVertical}
|
||||
disabled={shouldForbidRemoveAsDefault}
|
||||
/>
|
||||
</StyledLightIconButtonContainer>
|
||||
}
|
||||
dropdownComponents={
|
||||
shouldForbidRemoveAsDefault ? null : (
|
||||
|
||||
+35
-27
@@ -21,13 +21,17 @@ type SettingsDataModelFieldPreviewWidgetProps = {
|
||||
fullWidth?: boolean;
|
||||
};
|
||||
|
||||
const StyledCard = styled(Card)`
|
||||
border-radius: ${themeCssVariables.border.radius.md};
|
||||
color: ${themeCssVariables.font.color.primary};
|
||||
const StyledCardContainer = styled.div`
|
||||
> * {
|
||||
border-radius: ${themeCssVariables.border.radius.md};
|
||||
color: ${themeCssVariables.font.color.primary};
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledCardContent = styled(CardContent)`
|
||||
padding: ${themeCssVariables.spacing[2]};
|
||||
const StyledCardContentContainer = styled.div`
|
||||
> * {
|
||||
padding: ${themeCssVariables.spacing[2]};
|
||||
}
|
||||
`;
|
||||
|
||||
export const SettingsDataModelFieldPreviewWidget = ({
|
||||
@@ -43,27 +47,31 @@ export const SettingsDataModelFieldPreviewWidget = ({
|
||||
});
|
||||
|
||||
return (
|
||||
<StyledCard className={className} fullWidth>
|
||||
<StyledCardContent>
|
||||
<SettingsDataModelObjectPreview
|
||||
objectMetadataItems={[objectMetadataItem]}
|
||||
pluralizeLabel={pluralizeLabel}
|
||||
/>
|
||||
<SettingsDataModelFieldPreview
|
||||
objectNameSingular={objectNameSingular}
|
||||
fieldMetadataItem={{
|
||||
label: fieldMetadataItem.label,
|
||||
icon: fieldMetadataItem.icon,
|
||||
defaultValue: fieldMetadataItem.defaultValue,
|
||||
options: fieldMetadataItem.options,
|
||||
settings: fieldMetadataItem.settings,
|
||||
type: fieldMetadataItem.type,
|
||||
name: computeMetadataNameFromLabel(fieldMetadataItem.label),
|
||||
}}
|
||||
shrink={shrink}
|
||||
withFieldLabel={withFieldLabel}
|
||||
/>
|
||||
</StyledCardContent>
|
||||
</StyledCard>
|
||||
<StyledCardContainer className={className}>
|
||||
<Card fullWidth>
|
||||
<StyledCardContentContainer>
|
||||
<CardContent>
|
||||
<SettingsDataModelObjectPreview
|
||||
objectMetadataItems={[objectMetadataItem]}
|
||||
pluralizeLabel={pluralizeLabel}
|
||||
/>
|
||||
<SettingsDataModelFieldPreview
|
||||
objectNameSingular={objectNameSingular}
|
||||
fieldMetadataItem={{
|
||||
label: fieldMetadataItem.label,
|
||||
icon: fieldMetadataItem.icon,
|
||||
defaultValue: fieldMetadataItem.defaultValue,
|
||||
options: fieldMetadataItem.options,
|
||||
settings: fieldMetadataItem.settings,
|
||||
type: fieldMetadataItem.type,
|
||||
name: computeMetadataNameFromLabel(fieldMetadataItem.label),
|
||||
}}
|
||||
shrink={shrink}
|
||||
withFieldLabel={withFieldLabel}
|
||||
/>
|
||||
</CardContent>
|
||||
</StyledCardContentContainer>
|
||||
</Card>
|
||||
</StyledCardContainer>
|
||||
);
|
||||
};
|
||||
|
||||
+30
-21
@@ -21,14 +21,19 @@ export type SettingsDataModelRelationFieldPreviewSubWidgetProps = {
|
||||
pluralizeLabel?: boolean;
|
||||
};
|
||||
|
||||
const StyledCard = styled(Card)`
|
||||
border-radius: ${themeCssVariables.border.radius.md};
|
||||
color: ${themeCssVariables.font.color.primary};
|
||||
const StyledCardContainer = styled.div`
|
||||
margin: auto;
|
||||
|
||||
> * {
|
||||
border-radius: ${themeCssVariables.border.radius.md};
|
||||
color: ${themeCssVariables.font.color.primary};
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledCardContent = styled(CardContent)`
|
||||
padding: ${themeCssVariables.spacing[2]};
|
||||
const StyledCardContentContainer = styled.div`
|
||||
> * {
|
||||
padding: ${themeCssVariables.spacing[2]};
|
||||
}
|
||||
`;
|
||||
|
||||
export const SettingsDataModelRelationFieldPreviewSubWidget = ({
|
||||
@@ -49,21 +54,25 @@ export const SettingsDataModelRelationFieldPreviewSubWidget = ({
|
||||
.filter(isDefined);
|
||||
|
||||
return (
|
||||
<StyledCard className={className} fullWidth>
|
||||
<StyledCardContent>
|
||||
<SettingsDataModelObjectPreview
|
||||
objectMetadataItems={targetObjectMetadataItems}
|
||||
pluralizeLabel={pluralizeLabel}
|
||||
/>
|
||||
<SettingsDataModelRelationFieldPreview
|
||||
fieldMetadataItem={fieldMetadataItem}
|
||||
relationTargetObjectNameSingular={
|
||||
fieldPreviewTargetObjectNameSingular
|
||||
}
|
||||
shrink={shrink}
|
||||
withFieldLabel={withFieldLabel}
|
||||
/>
|
||||
</StyledCardContent>
|
||||
</StyledCard>
|
||||
<StyledCardContainer className={className}>
|
||||
<Card fullWidth>
|
||||
<StyledCardContentContainer>
|
||||
<CardContent>
|
||||
<SettingsDataModelObjectPreview
|
||||
objectMetadataItems={targetObjectMetadataItems}
|
||||
pluralizeLabel={pluralizeLabel}
|
||||
/>
|
||||
<SettingsDataModelRelationFieldPreview
|
||||
fieldMetadataItem={fieldMetadataItem}
|
||||
relationTargetObjectNameSingular={
|
||||
fieldPreviewTargetObjectNameSingular
|
||||
}
|
||||
shrink={shrink}
|
||||
withFieldLabel={withFieldLabel}
|
||||
/>
|
||||
</CardContent>
|
||||
</StyledCardContentContainer>
|
||||
</Card>
|
||||
</StyledCardContainer>
|
||||
);
|
||||
};
|
||||
|
||||
+20
-16
@@ -86,15 +86,17 @@ const StyledObjectInstanceCount = styled.div`
|
||||
color: ${themeCssVariables.font.color.tertiary};
|
||||
`;
|
||||
|
||||
const StyledObjectLink = styled(Link)`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
gap: ${themeCssVariables.spacing[1]};
|
||||
text-decoration: none;
|
||||
color: ${themeCssVariables.font.color.primary};
|
||||
const StyledObjectLinkContainer = styled.div`
|
||||
> a {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
gap: ${themeCssVariables.spacing[1]};
|
||||
text-decoration: none;
|
||||
color: ${themeCssVariables.font.color.primary};
|
||||
|
||||
&:hover {
|
||||
color: ${themeCssVariables.font.color.secondary};
|
||||
&:hover {
|
||||
color: ${themeCssVariables.font.color.secondary};
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -123,14 +125,16 @@ export const SettingsDataModelOverviewObject = ({
|
||||
<StyledNode>
|
||||
<StyledHeader>
|
||||
<StyledObjectName onMouseEnter={() => {}} onMouseLeave={() => {}}>
|
||||
<StyledObjectLink
|
||||
to={getSettingsPath(SettingsPath.Objects, {
|
||||
objectNamePlural: objectMetadataItem.namePlural,
|
||||
})}
|
||||
>
|
||||
{Icon && <Icon size={theme.icon.size.md} />}
|
||||
{objectMetadataItem.labelPlural}
|
||||
</StyledObjectLink>
|
||||
<StyledObjectLinkContainer>
|
||||
<Link
|
||||
to={getSettingsPath(SettingsPath.Objects, {
|
||||
objectNamePlural: objectMetadataItem.namePlural,
|
||||
})}
|
||||
>
|
||||
{Icon && <Icon size={theme.icon.size.md} />}
|
||||
{objectMetadataItem.labelPlural}
|
||||
</Link>
|
||||
</StyledObjectLinkContainer>
|
||||
<StyledObjectInstanceCount> · {totalCount}</StyledObjectInstanceCount>
|
||||
</StyledObjectName>
|
||||
<SettingsItemTypeTag item={objectMetadataItem} />
|
||||
|
||||
+17
-23
@@ -1,34 +1,21 @@
|
||||
import { styled } from '@linaria/react';
|
||||
|
||||
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { TableCell } from '@/ui/layout/table/components/TableCell';
|
||||
import { TableRow } from '@/ui/layout/table/components/TableRow';
|
||||
import { Checkbox } from 'twenty-ui/input';
|
||||
import { useIcons } from 'twenty-ui/display';
|
||||
import { styled } from '@linaria/react';
|
||||
import { useContext } from 'react';
|
||||
import { ThemeContext, themeCssVariables } from 'twenty-ui/theme-constants';
|
||||
|
||||
export const AVAILABLE_STANDARD_OBJECTS_GRID_TEMPLATE_COLUMNS =
|
||||
'28px 148px 256px 80px';
|
||||
|
||||
type SettingsAvailableStandardObjectItemTableRowProps = {
|
||||
isSelected?: boolean;
|
||||
objectItem: ObjectMetadataItem;
|
||||
onClick?: () => void;
|
||||
};
|
||||
|
||||
export const StyledAvailableStandardObjectTableRow = styled(TableRow)`
|
||||
grid-template-columns: 28px 148px 256px 80px;
|
||||
`;
|
||||
|
||||
const StyledCheckboxTableCell = styled(TableCell)`
|
||||
justify-content: center;
|
||||
padding: 0;
|
||||
padding-left: ${themeCssVariables.spacing[1]};
|
||||
`;
|
||||
|
||||
const StyledNameTableCell = styled(TableCell)`
|
||||
color: ${themeCssVariables.font.color.primary};
|
||||
gap: ${themeCssVariables.spacing[2]};
|
||||
`;
|
||||
|
||||
const StyledDescription = styled.div`
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
@@ -45,22 +32,29 @@ export const SettingsAvailableStandardObjectItemTableRow = ({
|
||||
const Icon = getIcon(objectItem.icon);
|
||||
|
||||
return (
|
||||
<StyledAvailableStandardObjectTableRow
|
||||
<TableRow
|
||||
gridTemplateColumns={AVAILABLE_STANDARD_OBJECTS_GRID_TEMPLATE_COLUMNS}
|
||||
key={objectItem.namePlural}
|
||||
isSelected={isSelected}
|
||||
onClick={onClick}
|
||||
>
|
||||
<StyledCheckboxTableCell>
|
||||
<TableCell
|
||||
align="center"
|
||||
padding={`0 0 0 ${themeCssVariables.spacing[1]}`}
|
||||
>
|
||||
<Checkbox checked={!!isSelected} />
|
||||
</StyledCheckboxTableCell>
|
||||
<StyledNameTableCell>
|
||||
</TableCell>
|
||||
<TableCell
|
||||
color={themeCssVariables.font.color.primary}
|
||||
gap={themeCssVariables.spacing[2]}
|
||||
>
|
||||
{!!Icon && <Icon size={theme.icon.size.md} />}
|
||||
{objectItem.labelPlural}
|
||||
</StyledNameTableCell>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<StyledDescription>{objectItem.description}</StyledDescription>
|
||||
</TableCell>
|
||||
<TableCell align="right">{objectItem.fields.length}</TableCell>
|
||||
</StyledAvailableStandardObjectTableRow>
|
||||
</TableRow>
|
||||
);
|
||||
};
|
||||
|
||||
+6
-3
@@ -6,9 +6,10 @@ import { TableHeader } from '@/ui/layout/table/components/TableHeader';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { H2Title } from 'twenty-ui/display';
|
||||
import { Section } from 'twenty-ui/layout';
|
||||
import { TableRow } from '@/ui/layout/table/components/TableRow';
|
||||
import {
|
||||
AVAILABLE_STANDARD_OBJECTS_GRID_TEMPLATE_COLUMNS,
|
||||
SettingsAvailableStandardObjectItemTableRow,
|
||||
StyledAvailableStandardObjectTableRow,
|
||||
} from './SettingsAvailableStandardObjectItemTableRow';
|
||||
|
||||
type SettingsAvailableStandardObjectsSectionProps = {
|
||||
@@ -28,12 +29,14 @@ export const SettingsAvailableStandardObjectsSection = ({
|
||||
description={t`Select one or several standard objects to activate below`}
|
||||
/>
|
||||
<Table>
|
||||
<StyledAvailableStandardObjectTableRow>
|
||||
<TableRow
|
||||
gridTemplateColumns={AVAILABLE_STANDARD_OBJECTS_GRID_TEMPLATE_COLUMNS}
|
||||
>
|
||||
<TableHeader></TableHeader>
|
||||
<TableHeader>{t`Name`}</TableHeader>
|
||||
<TableHeader>{t`Description`}</TableHeader>
|
||||
<TableHeader align="right">{t`Fields`}</TableHeader>
|
||||
</StyledAvailableStandardObjectTableRow>
|
||||
</TableRow>
|
||||
<TableBody>
|
||||
{objectItems.map((objectItem) => (
|
||||
<SettingsAvailableStandardObjectItemTableRow
|
||||
|
||||
+24
-24
@@ -36,14 +36,8 @@ type SettingsObjectFieldItemTableRowProps = {
|
||||
mode: 'view' | 'new-field';
|
||||
};
|
||||
|
||||
export const StyledObjectFieldTableRow = styled(TableRow)`
|
||||
grid-template-columns: minmax(0, 1fr) 148px 148px 36px;
|
||||
`;
|
||||
|
||||
const StyledNameTableCell = styled(TableCell)`
|
||||
color: ${themeCssVariables.font.color.primary};
|
||||
gap: ${themeCssVariables.spacing[2]};
|
||||
`;
|
||||
export const OBJECT_FIELD_TABLE_ROW_GRID_TEMPLATE_COLUMNS =
|
||||
'minmax(0, 1fr) 148px 148px 36px';
|
||||
|
||||
const StyledNameContainer = styled.div`
|
||||
display: flex;
|
||||
@@ -74,13 +68,10 @@ const StyledInactiveLabel = styled.span`
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledIconTableCell = styled(TableCell)`
|
||||
justify-content: center;
|
||||
padding-right: ${themeCssVariables.spacing[1]};
|
||||
`;
|
||||
|
||||
const StyledIconChevronRight = styled(IconChevronRight)`
|
||||
const StyledIconChevronRightContainer = styled.span`
|
||||
align-items: center;
|
||||
color: ${themeCssVariables.font.color.tertiary};
|
||||
display: flex;
|
||||
`;
|
||||
|
||||
export const SettingsObjectFieldItemTableRow = ({
|
||||
@@ -177,11 +168,15 @@ export const SettingsObjectFieldItemTableRow = ({
|
||||
: relationObjectMetadataItem?.labelPlural;
|
||||
|
||||
return (
|
||||
<StyledObjectFieldTableRow
|
||||
<TableRow
|
||||
gridTemplateColumns={OBJECT_FIELD_TABLE_ROW_GRID_TEMPLATE_COLUMNS}
|
||||
onClick={mode === 'view' ? navigateToFieldEdit : undefined}
|
||||
>
|
||||
<UndecoratedLink to={linkToNavigate}>
|
||||
<StyledNameTableCell>
|
||||
<TableCell
|
||||
color={themeCssVariables.font.color.primary}
|
||||
gap={themeCssVariables.spacing[2]}
|
||||
>
|
||||
{!!Icon && (
|
||||
<Icon
|
||||
style={{
|
||||
@@ -199,7 +194,7 @@ export const SettingsObjectFieldItemTableRow = ({
|
||||
<StyledInactiveLabel>{t`Deactivated`}</StyledInactiveLabel>
|
||||
)}
|
||||
</StyledNameContainer>
|
||||
</StyledNameTableCell>
|
||||
</TableCell>
|
||||
</UndecoratedLink>
|
||||
|
||||
<TableCell>
|
||||
@@ -232,14 +227,19 @@ export const SettingsObjectFieldItemTableRow = ({
|
||||
}}
|
||||
/>
|
||||
</TableCell>
|
||||
<StyledIconTableCell>
|
||||
<TableCell
|
||||
align="center"
|
||||
padding={`0 ${themeCssVariables.spacing[1]} 0 ${themeCssVariables.spacing[2]}`}
|
||||
>
|
||||
{status === 'active' ? (
|
||||
mode === 'view' ? (
|
||||
<UndecoratedLink to={linkToNavigate}>
|
||||
<StyledIconChevronRight
|
||||
size={theme.icon.size.md}
|
||||
stroke={theme.icon.stroke.sm}
|
||||
/>
|
||||
<StyledIconChevronRightContainer>
|
||||
<IconChevronRight
|
||||
size={theme.icon.size.md}
|
||||
stroke={theme.icon.stroke.sm}
|
||||
/>
|
||||
</StyledIconChevronRightContainer>
|
||||
</UndecoratedLink>
|
||||
) : (
|
||||
canToggleField && (
|
||||
@@ -273,7 +273,7 @@ export const SettingsObjectFieldItemTableRow = ({
|
||||
onClick={handleToggleField}
|
||||
/>
|
||||
)}
|
||||
</StyledIconTableCell>
|
||||
</StyledObjectFieldTableRow>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
};
|
||||
|
||||
+8
-3
@@ -5,10 +5,11 @@ import { useLingui } from '@lingui/react/macro';
|
||||
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { isHiddenSystemField } from '@/object-metadata/utils/isHiddenSystemField';
|
||||
import { SettingsItemTypeTag } from '@/settings/components/SettingsItemTypeTag';
|
||||
import { TableRow } from '@/ui/layout/table/components/TableRow';
|
||||
import {
|
||||
SETTINGS_OBJECT_TABLE_ROW_GRID_TEMPLATE_COLUMNS,
|
||||
StyledActionTableCell,
|
||||
StyledNameTableCell,
|
||||
StyledObjectTableRow,
|
||||
} from '@/settings/data-model/object-details/components/SettingsObjectItemTableRowStyledComponents';
|
||||
import { TableCell } from '@/ui/layout/table/components/TableCell';
|
||||
import { useIcons } from 'twenty-ui/display';
|
||||
@@ -63,7 +64,11 @@ export const SettingsObjectMetadataItemTableRow = ({
|
||||
const Icon = getIcon(objectMetadataItem.icon);
|
||||
|
||||
return (
|
||||
<StyledObjectTableRow key={objectMetadataItem.namePlural} to={link}>
|
||||
<TableRow
|
||||
gridTemplateColumns={SETTINGS_OBJECT_TABLE_ROW_GRID_TEMPLATE_COLUMNS}
|
||||
key={objectMetadataItem.namePlural}
|
||||
to={link}
|
||||
>
|
||||
<StyledNameTableCell>
|
||||
{!!Icon && (
|
||||
<Icon
|
||||
@@ -95,6 +100,6 @@ export const SettingsObjectMetadataItemTableRow = ({
|
||||
</TableCell>
|
||||
<TableCell align="right">{totalObjectCount}</TableCell>
|
||||
<StyledActionTableCell>{action}</StyledActionTableCell>
|
||||
</StyledObjectTableRow>
|
||||
</TableRow>
|
||||
);
|
||||
};
|
||||
|
||||
-21
@@ -1,21 +0,0 @@
|
||||
import { styled } from '@linaria/react';
|
||||
|
||||
import { TableCell } from '@/ui/layout/table/components/TableCell';
|
||||
import { TableRow } from '@/ui/layout/table/components/TableRow';
|
||||
import { themeCssVariables } from 'twenty-ui/theme-constants';
|
||||
|
||||
export const SETTINGS_OBJECT_TABLE_COLUMN_WIDTH = '98.7px';
|
||||
|
||||
export const StyledObjectTableRow = styled(TableRow)`
|
||||
grid-template-columns: 180px ${SETTINGS_OBJECT_TABLE_COLUMN_WIDTH} ${SETTINGS_OBJECT_TABLE_COLUMN_WIDTH} ${SETTINGS_OBJECT_TABLE_COLUMN_WIDTH} 36px;
|
||||
`;
|
||||
|
||||
export const StyledNameTableCell = styled(TableCell)`
|
||||
color: ${themeCssVariables.font.color.primary};
|
||||
gap: ${themeCssVariables.spacing[2]};
|
||||
`;
|
||||
|
||||
export const StyledActionTableCell = styled(TableCell)`
|
||||
justify-content: center;
|
||||
padding-right: ${themeCssVariables.spacing[1]};
|
||||
`;
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
import { TableCell } from '@/ui/layout/table/components/TableCell';
|
||||
import { themeCssVariables } from 'twenty-ui/theme-constants';
|
||||
import React from 'react';
|
||||
|
||||
export const SETTINGS_OBJECT_TABLE_COLUMN_WIDTH = '98.7px';
|
||||
|
||||
export const SETTINGS_OBJECT_TABLE_ROW_GRID_TEMPLATE_COLUMNS = `180px ${SETTINGS_OBJECT_TABLE_COLUMN_WIDTH} ${SETTINGS_OBJECT_TABLE_COLUMN_WIDTH} ${SETTINGS_OBJECT_TABLE_COLUMN_WIDTH} 36px`;
|
||||
|
||||
export const StyledNameTableCell = (
|
||||
props: React.ComponentProps<typeof TableCell>,
|
||||
) => (
|
||||
<TableCell
|
||||
color={themeCssVariables.font.color.primary}
|
||||
gap={themeCssVariables.spacing[2]}
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
|
||||
export const StyledActionTableCell = (
|
||||
props: React.ComponentProps<typeof TableCell>,
|
||||
) => (
|
||||
<TableCell
|
||||
align="center"
|
||||
padding={`0 ${themeCssVariables.spacing[1]} 0 ${themeCssVariables.spacing[2]}`}
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
+51
-42
@@ -25,14 +25,8 @@ type SettingsObjectRelationItemTableRowProps = {
|
||||
objectMetadataItem: ObjectMetadataItem;
|
||||
};
|
||||
|
||||
export const StyledObjectRelationTableRow = styled(TableRow)`
|
||||
grid-template-columns: minmax(0, 1fr) 148px 148px 36px;
|
||||
`;
|
||||
|
||||
const StyledNameTableCell = styled(TableCell)`
|
||||
color: ${themeCssVariables.font.color.primary};
|
||||
gap: ${themeCssVariables.spacing[2]};
|
||||
`;
|
||||
export const OBJECT_RELATION_TABLE_ROW_GRID_TEMPLATE_COLUMNS =
|
||||
'minmax(0, 1fr) 148px 148px 36px';
|
||||
|
||||
const StyledNameContainer = styled.div`
|
||||
display: flex;
|
||||
@@ -63,13 +57,10 @@ const StyledInactiveLabel = styled.span`
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledIconTableCell = styled(TableCell)`
|
||||
justify-content: center;
|
||||
padding-right: ${themeCssVariables.spacing[1]};
|
||||
`;
|
||||
|
||||
const StyledIconChevronRight = styled(IconChevronRight)`
|
||||
const StyledIconChevronRightContainer = styled.span`
|
||||
align-items: center;
|
||||
color: ${themeCssVariables.font.color.tertiary};
|
||||
display: flex;
|
||||
`;
|
||||
|
||||
const StyledRelationType = styled.div`
|
||||
@@ -79,18 +70,24 @@ const StyledRelationType = styled.div`
|
||||
gap: ${themeCssVariables.spacing[1]};
|
||||
`;
|
||||
|
||||
const StyledLink = styled(Link)`
|
||||
color: ${themeCssVariables.font.color.primary};
|
||||
text-decoration: underline;
|
||||
text-decoration-color: ${themeCssVariables.border.color.strong};
|
||||
text-underline-offset: 2px;
|
||||
const StyledLinkContainer = styled.div`
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
|
||||
&:hover {
|
||||
color: ${themeCssVariables.color.blue};
|
||||
text-decoration-color: ${themeCssVariables.color.blue};
|
||||
> a {
|
||||
color: ${themeCssVariables.font.color.primary};
|
||||
text-decoration: underline;
|
||||
text-decoration-color: ${themeCssVariables.border.color.strong};
|
||||
text-underline-offset: 2px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
|
||||
&:hover {
|
||||
color: ${themeCssVariables.color.blue};
|
||||
text-decoration-color: ${themeCssVariables.color.blue};
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -157,9 +154,14 @@ export const SettingsObjectRelationItemTableRow = ({
|
||||
: fieldMetadataItem.label;
|
||||
|
||||
return (
|
||||
// eslint-disable-next-line twenty/no-navigate-prefer-link
|
||||
<StyledObjectRelationTableRow onClick={navigateToFieldEdit}>
|
||||
<StyledNameTableCell>
|
||||
<TableRow
|
||||
gridTemplateColumns={OBJECT_RELATION_TABLE_ROW_GRID_TEMPLATE_COLUMNS}
|
||||
to={linkToNavigate}
|
||||
>
|
||||
<TableCell
|
||||
color={themeCssVariables.font.color.primary}
|
||||
gap={themeCssVariables.spacing[2]}
|
||||
>
|
||||
{!!Icon && (
|
||||
<Icon
|
||||
style={{
|
||||
@@ -171,15 +173,17 @@ export const SettingsObjectRelationItemTableRow = ({
|
||||
)}
|
||||
<StyledNameContainer>
|
||||
{isRelatedObjectLinkable ? (
|
||||
<StyledLink
|
||||
to={getSettingsPath(SettingsPath.ObjectDetail, {
|
||||
objectNamePlural: relationObjectMetadataItem.namePlural,
|
||||
})}
|
||||
onClick={(event) => event.stopPropagation()}
|
||||
title={targetObjectLabel}
|
||||
>
|
||||
{targetObjectLabel}
|
||||
</StyledLink>
|
||||
<StyledLinkContainer>
|
||||
<Link
|
||||
to={getSettingsPath(SettingsPath.ObjectDetail, {
|
||||
objectNamePlural: relationObjectMetadataItem.namePlural,
|
||||
})}
|
||||
onClick={(event) => event.stopPropagation()}
|
||||
title={targetObjectLabel}
|
||||
>
|
||||
{targetObjectLabel}
|
||||
</Link>
|
||||
</StyledLinkContainer>
|
||||
) : (
|
||||
<StyledNameLabel title={targetObjectLabel}>
|
||||
{targetObjectLabel}
|
||||
@@ -189,7 +193,7 @@ export const SettingsObjectRelationItemTableRow = ({
|
||||
<StyledInactiveLabel>{t`Deactivated`}</StyledInactiveLabel>
|
||||
)}
|
||||
</StyledNameContainer>
|
||||
</StyledNameTableCell>
|
||||
</TableCell>
|
||||
|
||||
<TableCell>
|
||||
<SettingsItemTypeTag
|
||||
@@ -213,13 +217,18 @@ export const SettingsObjectRelationItemTableRow = ({
|
||||
</StyledRelationType>
|
||||
</TableCell>
|
||||
|
||||
<StyledIconTableCell>
|
||||
<TableCell
|
||||
align="center"
|
||||
padding={`0 ${themeCssVariables.spacing[1]} 0 ${themeCssVariables.spacing[2]}`}
|
||||
>
|
||||
{fieldMetadataItem.isActive ? (
|
||||
<UndecoratedLink to={linkToNavigate}>
|
||||
<StyledIconChevronRight
|
||||
size={theme.icon.size.md}
|
||||
stroke={theme.icon.stroke.sm}
|
||||
/>
|
||||
<StyledIconChevronRightContainer>
|
||||
<IconChevronRight
|
||||
size={theme.icon.size.md}
|
||||
stroke={theme.icon.stroke.sm}
|
||||
/>
|
||||
</StyledIconChevronRightContainer>
|
||||
</UndecoratedLink>
|
||||
) : (
|
||||
<SettingsObjectFieldInactiveActionDropdown
|
||||
@@ -238,7 +247,7 @@ export const SettingsObjectRelationItemTableRow = ({
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</StyledIconTableCell>
|
||||
</StyledObjectRelationTableRow>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
};
|
||||
|
||||
+16
-11
@@ -27,9 +27,10 @@ import { Button } from 'twenty-ui/input';
|
||||
import { MenuItemToggle } from 'twenty-ui/navigation';
|
||||
import { themeCssVariables } from 'twenty-ui/theme-constants';
|
||||
import { normalizeSearchText } from '~/utils/normalizeSearchText';
|
||||
import { TableRow } from '@/ui/layout/table/components/TableRow';
|
||||
import {
|
||||
OBJECT_RELATION_TABLE_ROW_GRID_TEMPLATE_COLUMNS,
|
||||
SettingsObjectRelationItemTableRow,
|
||||
StyledObjectRelationTableRow,
|
||||
} from './SettingsObjectRelationItemTableRow';
|
||||
|
||||
const StyledSearchAndFilterContainer = styled.div`
|
||||
@@ -39,7 +40,7 @@ const StyledSearchAndFilterContainer = styled.div`
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
const StyledSearchInput = styled(SettingsTextInput)`
|
||||
const StyledSearchInputContainer = styled.div`
|
||||
flex: 1;
|
||||
`;
|
||||
|
||||
@@ -118,13 +119,15 @@ export const SettingsObjectRelationsTable = ({
|
||||
return (
|
||||
<>
|
||||
<StyledSearchAndFilterContainer>
|
||||
<StyledSearchInput
|
||||
instanceId="object-relation-table-search"
|
||||
LeftIcon={IconSearch}
|
||||
placeholder={t`Search a field...`}
|
||||
value={searchTerm}
|
||||
onChange={setSearchTerm}
|
||||
/>
|
||||
<StyledSearchInputContainer>
|
||||
<SettingsTextInput
|
||||
instanceId="object-relation-table-search"
|
||||
LeftIcon={IconSearch}
|
||||
placeholder={t`Search a field...`}
|
||||
value={searchTerm}
|
||||
onChange={setSearchTerm}
|
||||
/>
|
||||
</StyledSearchInputContainer>
|
||||
<Dropdown
|
||||
dropdownId="settings-relations-filter-dropdown"
|
||||
dropdownPlacement="bottom-end"
|
||||
@@ -165,7 +168,9 @@ export const SettingsObjectRelationsTable = ({
|
||||
/>
|
||||
</StyledSearchAndFilterContainer>
|
||||
<Table>
|
||||
<StyledObjectRelationTableRow>
|
||||
<TableRow
|
||||
gridTemplateColumns={OBJECT_RELATION_TABLE_ROW_GRID_TEMPLATE_COLUMNS}
|
||||
>
|
||||
{tableMetadata.fields.map((item) => (
|
||||
<SortableTableHeader
|
||||
key={item.fieldName}
|
||||
@@ -176,7 +181,7 @@ export const SettingsObjectRelationsTable = ({
|
||||
/>
|
||||
))}
|
||||
<TableHeader></TableHeader>
|
||||
</StyledObjectRelationTableRow>
|
||||
</TableRow>
|
||||
{filteredRelationFields.map((fieldMetadataItem) => (
|
||||
<SettingsObjectRelationItemTableRow
|
||||
key={fieldMetadataItem.id}
|
||||
|
||||
+19
-15
@@ -29,8 +29,10 @@ const StyledContentContainer = styled.div`
|
||||
gap: ${themeCssVariables.spacing[8]};
|
||||
`;
|
||||
|
||||
const StyledFormSection = styled(Section)`
|
||||
padding-left: 0 !important;
|
||||
const StyledFormSectionContainer = styled.div`
|
||||
> * {
|
||||
padding-left: 0 !important;
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledDangerButtonsContainer = styled.div`
|
||||
@@ -90,16 +92,18 @@ export const ObjectSettings = ({
|
||||
|
||||
return (
|
||||
<StyledContentContainer>
|
||||
<StyledFormSection>
|
||||
<H2Title
|
||||
title={t`About`}
|
||||
description={t`Name in both singular (e.g., 'Invoice') and plural (e.g., 'Invoices') forms.`}
|
||||
/>
|
||||
<SettingsUpdateDataModelObjectAboutForm
|
||||
objectMetadataItem={objectMetadataItem}
|
||||
/>
|
||||
</StyledFormSection>
|
||||
<StyledFormSection>
|
||||
<StyledFormSectionContainer>
|
||||
<Section>
|
||||
<H2Title
|
||||
title={t`About`}
|
||||
description={t`Name in both singular (e.g., 'Invoice') and plural (e.g., 'Invoices') forms.`}
|
||||
/>
|
||||
<SettingsUpdateDataModelObjectAboutForm
|
||||
objectMetadataItem={objectMetadataItem}
|
||||
/>
|
||||
</Section>
|
||||
</StyledFormSectionContainer>
|
||||
<StyledFormSectionContainer>
|
||||
<Section>
|
||||
<H2Title
|
||||
title={t`Options`}
|
||||
@@ -109,9 +113,9 @@ export const ObjectSettings = ({
|
||||
objectMetadataItem={objectMetadataItem}
|
||||
/>
|
||||
</Section>
|
||||
</StyledFormSection>
|
||||
</StyledFormSectionContainer>
|
||||
{!isReadOnly && (
|
||||
<StyledFormSection>
|
||||
<StyledFormSectionContainer>
|
||||
<Section>
|
||||
<H2Title
|
||||
title={t`Danger zone`}
|
||||
@@ -136,7 +140,7 @@ export const ObjectSettings = ({
|
||||
)}
|
||||
</StyledDangerButtonsContainer>
|
||||
</Section>
|
||||
</StyledFormSection>
|
||||
</StyledFormSectionContainer>
|
||||
)}
|
||||
<ConfirmationModal
|
||||
modalInstanceId={DELETE_OBJECT_MODAL_ID}
|
||||
|
||||
+2
-2
@@ -6,12 +6,12 @@ import { SettingsPath } from 'twenty-shared/types';
|
||||
import { getSettingsPath } from 'twenty-shared/utils';
|
||||
import { IconEye } from 'twenty-ui/display';
|
||||
import { FloatingButton } from 'twenty-ui/input';
|
||||
import { Card } from 'twenty-ui/layout';
|
||||
|
||||
import DarkCoverImage from '@/settings/data-model/assets/cover-dark.png';
|
||||
import LightCoverImage from '@/settings/data-model/assets/cover-light.png';
|
||||
import { ThemeContext, themeCssVariables } from 'twenty-ui/theme-constants';
|
||||
const StyledCoverImageContainer = styled(Card)`
|
||||
|
||||
const StyledCoverImageContainer = styled.div`
|
||||
align-items: center;
|
||||
background-size: cover;
|
||||
border-radius: ${themeCssVariables.border.radius.md};
|
||||
|
||||
+24
-20
@@ -95,14 +95,16 @@ const StyledBannerText = styled.span`
|
||||
flex: 1;
|
||||
`;
|
||||
|
||||
const StyledConflictButton = styled(Button)`
|
||||
border-color: ${themeCssVariables.color.blue};
|
||||
color: ${themeCssVariables.color.blue};
|
||||
&:hover {
|
||||
background: ${themeCssVariables.accent.secondary};
|
||||
}
|
||||
&:focus-visible {
|
||||
box-shadow: 0 0 0 3px ${themeCssVariables.accent.tertiary};
|
||||
const StyledConflictButtonContainer = styled.div`
|
||||
> button {
|
||||
border-color: ${themeCssVariables.color.blue};
|
||||
color: ${themeCssVariables.color.blue};
|
||||
&:hover {
|
||||
background: ${themeCssVariables.accent.secondary};
|
||||
}
|
||||
&:focus-visible {
|
||||
box-shadow: 0 0 0 3px ${themeCssVariables.accent.tertiary};
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -281,18 +283,20 @@ export const SettingsDataModelObjectAboutForm = ({
|
||||
{t`An object with this name already exists`}
|
||||
</StyledBannerText>
|
||||
</StyledBannerContent>
|
||||
<StyledConflictButton
|
||||
size="small"
|
||||
variant="secondary"
|
||||
accent="blue"
|
||||
title={t`Open`}
|
||||
onClick={() =>
|
||||
navigateSettings(SettingsPath.ObjectDetail, {
|
||||
objectNamePlural:
|
||||
conflictingObjectMetadataItem.namePlural,
|
||||
})
|
||||
}
|
||||
/>
|
||||
<StyledConflictButtonContainer>
|
||||
<Button
|
||||
size="small"
|
||||
variant="secondary"
|
||||
accent="blue"
|
||||
title={t`Open`}
|
||||
onClick={() =>
|
||||
navigateSettings(SettingsPath.ObjectDetail, {
|
||||
objectNamePlural:
|
||||
conflictingObjectMetadataItem.namePlural,
|
||||
})
|
||||
}
|
||||
/>
|
||||
</StyledConflictButtonContainer>
|
||||
</StyledConflictBanner>
|
||||
)}
|
||||
{[
|
||||
|
||||
+40
-27
@@ -15,18 +15,25 @@ type SettingsDataModelObjectSettingsFormCardProps = {
|
||||
objectMetadataItem: ObjectMetadataItem;
|
||||
};
|
||||
|
||||
const StyledTopCardContent = styled(CardContent)`
|
||||
background-color: ${themeCssVariables.background.transparent.lighter};
|
||||
const StyledTopCardContentContainer = styled.div`
|
||||
> * {
|
||||
background-color: ${themeCssVariables.background.transparent.lighter};
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledObjectSummaryCard = styled(Card)`
|
||||
border-radius: ${themeCssVariables.border.radius.md};
|
||||
color: ${themeCssVariables.font.color.primary};
|
||||
const StyledObjectSummaryCardContainer = styled.div`
|
||||
max-width: 480px;
|
||||
|
||||
> * {
|
||||
border-radius: ${themeCssVariables.border.radius.md};
|
||||
color: ${themeCssVariables.font.color.primary};
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledObjectSummaryCardContent = styled(CardContent)`
|
||||
padding: ${themeCssVariables.spacing[2]};
|
||||
const StyledObjectSummaryCardContentContainer = styled.div`
|
||||
> * {
|
||||
padding: ${themeCssVariables.spacing[2]};
|
||||
}
|
||||
`;
|
||||
|
||||
export const SettingsDataModelObjectSettingsFormCard = ({
|
||||
@@ -42,26 +49,32 @@ export const SettingsDataModelObjectSettingsFormCard = ({
|
||||
|
||||
return (
|
||||
<Card fullWidth>
|
||||
<StyledTopCardContent divider>
|
||||
<SettingsDataModelCardTitle>
|
||||
<Trans>Preview</Trans>
|
||||
</SettingsDataModelCardTitle>
|
||||
{labelIdentifierFieldMetadataItem ? (
|
||||
<SettingsDataModelFieldPreviewWidget
|
||||
objectNameSingular={objectMetadataItem.nameSingular}
|
||||
fieldMetadataItem={labelIdentifierFieldMetadataItem}
|
||||
withFieldLabel={false}
|
||||
/>
|
||||
) : (
|
||||
<StyledObjectSummaryCard>
|
||||
<StyledObjectSummaryCardContent>
|
||||
<SettingsDataModelObjectPreview
|
||||
objectMetadataItems={[objectMetadataItem]}
|
||||
/>
|
||||
</StyledObjectSummaryCardContent>
|
||||
</StyledObjectSummaryCard>
|
||||
)}
|
||||
</StyledTopCardContent>
|
||||
<StyledTopCardContentContainer>
|
||||
<CardContent divider>
|
||||
<SettingsDataModelCardTitle>
|
||||
<Trans>Preview</Trans>
|
||||
</SettingsDataModelCardTitle>
|
||||
{labelIdentifierFieldMetadataItem ? (
|
||||
<SettingsDataModelFieldPreviewWidget
|
||||
objectNameSingular={objectMetadataItem.nameSingular}
|
||||
fieldMetadataItem={labelIdentifierFieldMetadataItem}
|
||||
withFieldLabel={false}
|
||||
/>
|
||||
) : (
|
||||
<StyledObjectSummaryCardContainer>
|
||||
<Card>
|
||||
<StyledObjectSummaryCardContentContainer>
|
||||
<CardContent>
|
||||
<SettingsDataModelObjectPreview
|
||||
objectMetadataItems={[objectMetadataItem]}
|
||||
/>
|
||||
</CardContent>
|
||||
</StyledObjectSummaryCardContentContainer>
|
||||
</Card>
|
||||
</StyledObjectSummaryCardContainer>
|
||||
)}
|
||||
</CardContent>
|
||||
</StyledTopCardContentContainer>
|
||||
<CardContent>
|
||||
<SettingsDataModelObjectIdentifiersForm
|
||||
objectMetadataItem={objectMetadataItem}
|
||||
|
||||
+26
-24
@@ -1,5 +1,3 @@
|
||||
import { styled } from '@linaria/react';
|
||||
|
||||
import {
|
||||
formatExpiration,
|
||||
isExpired,
|
||||
@@ -7,23 +5,11 @@ import {
|
||||
import { TableCell } from '@/ui/layout/table/components/TableCell';
|
||||
import { TableRow } from '@/ui/layout/table/components/TableRow';
|
||||
import { IconChevronRight } from 'twenty-ui/display';
|
||||
import { styled } from '@linaria/react';
|
||||
import { useContext } from 'react';
|
||||
import { MOBILE_VIEWPORT, ThemeContext } from 'twenty-ui/theme-constants';
|
||||
import { ThemeContext } from 'twenty-ui/theme-constants';
|
||||
import { type ApiKey } from '~/generated-metadata/graphql';
|
||||
|
||||
export const StyledApisFieldTableRow = styled(TableRow)`
|
||||
@media (max-width: ${MOBILE_VIEWPORT}px) {
|
||||
width: 100%;
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledTruncatedCell = styled(TableCell)`
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
cursor: pointer;
|
||||
`;
|
||||
|
||||
const StyledEllipsisLabel = styled.div`
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
@@ -49,24 +35,40 @@ export const SettingsApiKeysFieldItemTableRow = ({
|
||||
const gridColumns = '5fr 2fr 3fr 1fr';
|
||||
|
||||
return (
|
||||
<StyledApisFieldTableRow gridAutoColumns={gridColumns} to={to}>
|
||||
<StyledTruncatedCell color={theme.font.color.primary}>
|
||||
<TableRow gridAutoColumns={gridColumns} to={to}>
|
||||
<TableCell
|
||||
color={theme.font.color.primary}
|
||||
whiteSpace="nowrap"
|
||||
overflow="hidden"
|
||||
textOverflow="ellipsis"
|
||||
clickable
|
||||
>
|
||||
<StyledEllipsisLabel>{apiKey.name}</StyledEllipsisLabel>
|
||||
</StyledTruncatedCell>
|
||||
</TableCell>
|
||||
|
||||
<StyledTruncatedCell color={theme.font.color.tertiary}>
|
||||
<TableCell
|
||||
color={theme.font.color.tertiary}
|
||||
whiteSpace="nowrap"
|
||||
overflow="hidden"
|
||||
textOverflow="ellipsis"
|
||||
clickable
|
||||
>
|
||||
<StyledEllipsisLabel>{apiKey.role?.label || '-'}</StyledEllipsisLabel>
|
||||
</StyledTruncatedCell>
|
||||
</TableCell>
|
||||
|
||||
<StyledTruncatedCell
|
||||
<TableCell
|
||||
color={
|
||||
isExpired(apiKey.expiresAt || null)
|
||||
? theme.font.color.danger
|
||||
: theme.font.color.tertiary
|
||||
}
|
||||
whiteSpace="nowrap"
|
||||
overflow="hidden"
|
||||
textOverflow="ellipsis"
|
||||
clickable
|
||||
>
|
||||
<StyledEllipsisLabel>{formattedExpiration}</StyledEllipsisLabel>
|
||||
</StyledTruncatedCell>
|
||||
</TableCell>
|
||||
|
||||
<TableCell align="right">
|
||||
<IconChevronRight
|
||||
@@ -74,6 +76,6 @@ export const SettingsApiKeysFieldItemTableRow = ({
|
||||
color={theme.font.color.tertiary}
|
||||
/>
|
||||
</TableCell>
|
||||
</StyledApisFieldTableRow>
|
||||
</TableRow>
|
||||
);
|
||||
};
|
||||
|
||||
+14
-12
@@ -10,7 +10,7 @@ import { getSettingsPath } from 'twenty-shared/utils';
|
||||
import { themeCssVariables } from 'twenty-ui/theme-constants';
|
||||
import { useGetApiKeysQuery } from '~/generated-metadata/graphql';
|
||||
|
||||
const StyledTableBody = styled(TableBody)`
|
||||
const StyledTableBodyContainer = styled.div`
|
||||
border-bottom: 1px solid ${themeCssVariables.border.color.light};
|
||||
`;
|
||||
|
||||
@@ -36,17 +36,19 @@ export const SettingsApiKeysTable = () => {
|
||||
<TableHeader></TableHeader>
|
||||
</TableRow>
|
||||
{!!apiKeys?.length && (
|
||||
<StyledTableBody>
|
||||
{apiKeys.map((apiKey) => (
|
||||
<SettingsApiKeysFieldItemTableRow
|
||||
key={apiKey.id}
|
||||
apiKey={apiKey}
|
||||
to={getSettingsPath(SettingsPath.ApiKeyDetail, {
|
||||
apiKeyId: apiKey.id,
|
||||
})}
|
||||
/>
|
||||
))}
|
||||
</StyledTableBody>
|
||||
<StyledTableBodyContainer>
|
||||
<TableBody>
|
||||
{apiKeys.map((apiKey) => (
|
||||
<SettingsApiKeysFieldItemTableRow
|
||||
key={apiKey.id}
|
||||
apiKey={apiKey}
|
||||
to={getSettingsPath(SettingsPath.ApiKeyDetail, {
|
||||
apiKeyId: apiKey.id,
|
||||
})}
|
||||
/>
|
||||
))}
|
||||
</TableBody>
|
||||
</StyledTableBodyContainer>
|
||||
)}
|
||||
</Table>
|
||||
);
|
||||
|
||||
+22
-25
@@ -11,23 +11,12 @@ import { useContext } from 'react';
|
||||
import { ThemeContext, themeCssVariables } from 'twenty-ui/theme-constants';
|
||||
import { type Webhook } from '~/generated-metadata/graphql';
|
||||
|
||||
export const StyledApisFieldTableRow = styled(TableRow)`
|
||||
grid-template-columns: 1fr 28px;
|
||||
`;
|
||||
const WEBHOOK_TABLE_ROW_GRID_TEMPLATE_COLUMNS = '1fr 28px';
|
||||
|
||||
const StyledIconTableCell = styled(TableCell)`
|
||||
justify-content: center;
|
||||
padding-right: ${themeCssVariables.spacing[1]};
|
||||
padding-left: 0;
|
||||
`;
|
||||
|
||||
const StyledUrlTableCell = styled(TableCell)`
|
||||
color: ${themeCssVariables.font.color.primary};
|
||||
overflow: hidden;
|
||||
`;
|
||||
|
||||
const StyledIconChevronRight = styled(IconChevronRight)`
|
||||
const StyledIconChevronRightContainer = styled.span`
|
||||
align-items: center;
|
||||
color: ${themeCssVariables.font.color.tertiary};
|
||||
display: flex;
|
||||
`;
|
||||
|
||||
export const SettingsDevelopersWebhookTableRow = ({
|
||||
@@ -42,8 +31,11 @@ export const SettingsDevelopersWebhookTableRow = ({
|
||||
}) => {
|
||||
const { theme } = useContext(ThemeContext);
|
||||
return (
|
||||
<StyledApisFieldTableRow to={to}>
|
||||
<StyledUrlTableCell>
|
||||
<TableRow
|
||||
gridTemplateColumns={WEBHOOK_TABLE_ROW_GRID_TEMPLATE_COLUMNS}
|
||||
to={to}
|
||||
>
|
||||
<TableCell color={themeCssVariables.font.color.primary} overflow="hidden">
|
||||
<OverflowingTextWithTooltip
|
||||
text={
|
||||
isValidUrl(webhook.targetUrl)
|
||||
@@ -51,13 +43,18 @@ export const SettingsDevelopersWebhookTableRow = ({
|
||||
: webhook.targetUrl
|
||||
}
|
||||
/>
|
||||
</StyledUrlTableCell>
|
||||
<StyledIconTableCell>
|
||||
<StyledIconChevronRight
|
||||
size={theme.icon.size.md}
|
||||
stroke={theme.icon.stroke.sm}
|
||||
/>
|
||||
</StyledIconTableCell>
|
||||
</StyledApisFieldTableRow>
|
||||
</TableCell>
|
||||
<TableCell
|
||||
align="center"
|
||||
padding={`0 ${themeCssVariables.spacing[1]} 0 0`}
|
||||
>
|
||||
<StyledIconChevronRightContainer>
|
||||
<IconChevronRight
|
||||
size={theme.icon.size.md}
|
||||
stroke={theme.icon.stroke.sm}
|
||||
/>
|
||||
</StyledIconChevronRightContainer>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
};
|
||||
|
||||
+16
-18
@@ -10,16 +10,12 @@ import { getSettingsPath } from 'twenty-shared/utils';
|
||||
import { themeCssVariables } from 'twenty-ui/theme-constants';
|
||||
import { useGetWebhooksQuery } from '~/generated-metadata/graphql';
|
||||
|
||||
const StyledTableBody = styled(TableBody)`
|
||||
const StyledTableBodyContainer = styled.div`
|
||||
border-bottom: 1px solid ${themeCssVariables.border.color.light};
|
||||
max-height: 260px;
|
||||
overflow-y: auto;
|
||||
`;
|
||||
|
||||
const StyledTableRow = styled(TableRow)`
|
||||
grid-template-columns: 444px 68px;
|
||||
`;
|
||||
|
||||
export const SettingsWebhooksTable = () => {
|
||||
const { data: webhooksData } = useGetWebhooksQuery();
|
||||
|
||||
@@ -27,22 +23,24 @@ export const SettingsWebhooksTable = () => {
|
||||
|
||||
return (
|
||||
<Table>
|
||||
<StyledTableRow>
|
||||
<TableRow gridTemplateColumns="444px 68px">
|
||||
<TableHeader>URL</TableHeader>
|
||||
<TableHeader></TableHeader>
|
||||
</StyledTableRow>
|
||||
</TableRow>
|
||||
{!!webhooks?.length && (
|
||||
<StyledTableBody>
|
||||
{webhooks.map((webhookFieldItem) => (
|
||||
<SettingsDevelopersWebhookTableRow
|
||||
key={webhookFieldItem.id}
|
||||
webhook={webhookFieldItem}
|
||||
to={getSettingsPath(SettingsPath.WebhookDetail, {
|
||||
webhookId: webhookFieldItem.id,
|
||||
})}
|
||||
/>
|
||||
))}
|
||||
</StyledTableBody>
|
||||
<StyledTableBodyContainer>
|
||||
<TableBody>
|
||||
{webhooks.map((webhookFieldItem) => (
|
||||
<SettingsDevelopersWebhookTableRow
|
||||
key={webhookFieldItem.id}
|
||||
webhook={webhookFieldItem}
|
||||
to={getSettingsPath(SettingsPath.WebhookDetail, {
|
||||
webhookId: webhookFieldItem.id,
|
||||
})}
|
||||
/>
|
||||
))}
|
||||
</TableBody>
|
||||
</StyledTableBodyContainer>
|
||||
)}
|
||||
</Table>
|
||||
);
|
||||
|
||||
+6
-5
@@ -60,13 +60,15 @@ const StyledControlLabel = styled.span`
|
||||
white-space: nowrap;
|
||||
`;
|
||||
|
||||
const StyledControlIconChevronDown = styled(IconChevronDown)<{
|
||||
const StyledControlIconChevronDownContainer = styled.span<{
|
||||
disabled?: boolean;
|
||||
}>`
|
||||
align-items: center;
|
||||
color: ${({ disabled }) =>
|
||||
disabled
|
||||
? themeCssVariables.font.color.extraLight
|
||||
: themeCssVariables.font.color.tertiary};
|
||||
display: flex;
|
||||
`;
|
||||
|
||||
type WebhookEntitySelectProps = {
|
||||
@@ -166,10 +168,9 @@ export const WebhookEntitySelect = ({
|
||||
clickableComponent={
|
||||
<StyledControlContainer disabled={disabled}>
|
||||
<StyledControlLabel>{getSelectedLabel()}</StyledControlLabel>
|
||||
<StyledControlIconChevronDown
|
||||
disabled={disabled}
|
||||
size={theme.icon.size.md}
|
||||
/>
|
||||
<StyledControlIconChevronDownContainer disabled={disabled}>
|
||||
<IconChevronDown size={theme.icon.size.md} />
|
||||
</StyledControlIconChevronDownContainer>
|
||||
</StyledControlContainer>
|
||||
}
|
||||
dropdownComponents={
|
||||
|
||||
+26
-20
@@ -25,13 +25,13 @@ import { SaveAndCancelButtons } from '@/settings/components/SaveAndCancelButtons
|
||||
import { getDomainValidationSchema } from '@/settings/domains/utils/get-domain-validation-schema';
|
||||
import { themeCssVariables } from 'twenty-ui/theme-constants';
|
||||
|
||||
const StyledButtonGroup = styled(ButtonGroup)`
|
||||
& > :not(:first-of-type) > button {
|
||||
const StyledButtonGroupContainer = styled.div`
|
||||
> * > :not(:first-of-type) > button {
|
||||
border-left: none;
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledButton = styled(Button)`
|
||||
const StyledButtonContainer = styled.div`
|
||||
align-self: flex-start;
|
||||
`;
|
||||
|
||||
@@ -171,23 +171,29 @@ export const SettingPublicDomain = () => {
|
||||
fullWidth
|
||||
/>
|
||||
{isDefined(selectedPublicDomain) && (
|
||||
<StyledButtonGroup>
|
||||
<StyledButton
|
||||
isLoading={isLoading}
|
||||
Icon={IconReload}
|
||||
title={t`Reload`}
|
||||
variant="primary"
|
||||
onClick={() =>
|
||||
checkPublicDomainRecords(selectedPublicDomain.domain)
|
||||
}
|
||||
type="button"
|
||||
/>
|
||||
<StyledButton
|
||||
Icon={IconTrash}
|
||||
variant="primary"
|
||||
onClick={onDelete}
|
||||
/>
|
||||
</StyledButtonGroup>
|
||||
<StyledButtonGroupContainer>
|
||||
<ButtonGroup>
|
||||
<StyledButtonContainer>
|
||||
<Button
|
||||
isLoading={isLoading}
|
||||
Icon={IconReload}
|
||||
title={t`Reload`}
|
||||
variant="primary"
|
||||
onClick={() =>
|
||||
checkPublicDomainRecords(selectedPublicDomain.domain)
|
||||
}
|
||||
type="button"
|
||||
/>
|
||||
</StyledButtonContainer>
|
||||
<StyledButtonContainer>
|
||||
<Button
|
||||
Icon={IconTrash}
|
||||
variant="primary"
|
||||
onClick={onDelete}
|
||||
/>
|
||||
</StyledButtonContainer>
|
||||
</ButtonGroup>
|
||||
</StyledButtonGroupContainer>
|
||||
)}
|
||||
</StyledDomainFormWrapper>
|
||||
{isDefined(selectedPublicDomain) && publicDomainRecords?.domain && (
|
||||
|
||||
+24
-18
@@ -19,13 +19,13 @@ const StyledDomainFormWrapper = styled.div`
|
||||
gap: ${themeCssVariables.spacing[2]};
|
||||
`;
|
||||
|
||||
const StyledButtonGroup = styled(ButtonGroup)`
|
||||
& > :not(:first-of-type) > button {
|
||||
const StyledButtonGroupContainer = styled.div`
|
||||
> * > :not(:first-of-type) > button {
|
||||
border-left: none;
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledButton = styled(Button)`
|
||||
const StyledButtonContainer = styled.div`
|
||||
align-self: flex-start;
|
||||
`;
|
||||
|
||||
@@ -80,21 +80,27 @@ export const SettingsCustomDomain = () => {
|
||||
)}
|
||||
/>
|
||||
{currentWorkspace?.customDomain && (
|
||||
<StyledButtonGroup>
|
||||
<StyledButton
|
||||
isLoading={isLoading}
|
||||
Icon={IconReload}
|
||||
title={t`Reload`}
|
||||
variant="primary"
|
||||
onClick={checkCustomDomainRecords}
|
||||
type="button"
|
||||
/>
|
||||
<StyledButton
|
||||
Icon={IconTrash}
|
||||
variant="primary"
|
||||
onClick={deleteCustomDomain}
|
||||
/>
|
||||
</StyledButtonGroup>
|
||||
<StyledButtonGroupContainer>
|
||||
<ButtonGroup>
|
||||
<StyledButtonContainer>
|
||||
<Button
|
||||
isLoading={isLoading}
|
||||
Icon={IconReload}
|
||||
title={t`Reload`}
|
||||
variant="primary"
|
||||
onClick={checkCustomDomainRecords}
|
||||
type="button"
|
||||
/>
|
||||
</StyledButtonContainer>
|
||||
<StyledButtonContainer>
|
||||
<Button
|
||||
Icon={IconTrash}
|
||||
variant="primary"
|
||||
onClick={deleteCustomDomain}
|
||||
/>
|
||||
</StyledButtonContainer>
|
||||
</ButtonGroup>
|
||||
</StyledButtonGroupContainer>
|
||||
)}
|
||||
</StyledDomainFormWrapper>
|
||||
{currentWorkspace?.customDomain && (
|
||||
|
||||
+30
-25
@@ -6,23 +6,10 @@ import { StyledTableRow } from '@/settings/logic-functions/components/SettingsLo
|
||||
import { useContext } from 'react';
|
||||
import { ThemeContext, themeCssVariables } from 'twenty-ui/theme-constants';
|
||||
|
||||
const StyledNameTableCell = styled(TableCell)`
|
||||
color: ${themeCssVariables.font.color.primary};
|
||||
gap: ${themeCssVariables.spacing[2]};
|
||||
`;
|
||||
|
||||
const StyledRuntimeTableCell = styled(TableCell)`
|
||||
color: ${themeCssVariables.font.color.secondary};
|
||||
gap: ${themeCssVariables.spacing[2]};
|
||||
`;
|
||||
|
||||
const StyledIconTableCell = styled(TableCell)`
|
||||
justify-content: center;
|
||||
padding-right: ${themeCssVariables.spacing[1]};
|
||||
`;
|
||||
|
||||
const StyledIconChevronRight = styled(IconChevronRight)`
|
||||
const StyledIconChevronRightContainer = styled.span`
|
||||
align-items: center;
|
||||
color: ${themeCssVariables.font.color.tertiary};
|
||||
display: flex;
|
||||
`;
|
||||
|
||||
export const SettingsLogicFunctionsFieldItemTableRow = ({
|
||||
@@ -35,15 +22,33 @@ export const SettingsLogicFunctionsFieldItemTableRow = ({
|
||||
const { theme } = useContext(ThemeContext);
|
||||
return (
|
||||
<StyledTableRow to={to}>
|
||||
<StyledNameTableCell>{logicFunction.name}</StyledNameTableCell>
|
||||
<StyledNameTableCell></StyledNameTableCell>
|
||||
<StyledRuntimeTableCell>{logicFunction.runtime}</StyledRuntimeTableCell>
|
||||
<StyledIconTableCell>
|
||||
<StyledIconChevronRight
|
||||
size={theme.icon.size.md}
|
||||
stroke={theme.icon.stroke.sm}
|
||||
/>
|
||||
</StyledIconTableCell>
|
||||
<TableCell
|
||||
color={themeCssVariables.font.color.primary}
|
||||
gap={themeCssVariables.spacing[2]}
|
||||
>
|
||||
{logicFunction.name}
|
||||
</TableCell>
|
||||
<TableCell
|
||||
color={themeCssVariables.font.color.primary}
|
||||
gap={themeCssVariables.spacing[2]}
|
||||
></TableCell>
|
||||
<TableCell
|
||||
color={themeCssVariables.font.color.secondary}
|
||||
gap={themeCssVariables.spacing[2]}
|
||||
>
|
||||
{logicFunction.runtime}
|
||||
</TableCell>
|
||||
<TableCell
|
||||
align="center"
|
||||
padding={`0 ${themeCssVariables.spacing[1]} 0 ${themeCssVariables.spacing[2]}`}
|
||||
>
|
||||
<StyledIconChevronRightContainer>
|
||||
<IconChevronRight
|
||||
size={theme.icon.size.md}
|
||||
stroke={theme.icon.stroke.sm}
|
||||
/>
|
||||
</StyledIconChevronRightContainer>
|
||||
</TableCell>
|
||||
</StyledTableRow>
|
||||
);
|
||||
};
|
||||
|
||||
+25
-16
@@ -8,14 +8,21 @@ import { SettingsPath } from 'twenty-shared/types';
|
||||
import { getSettingsPath } from 'twenty-shared/utils';
|
||||
import { type LogicFunction } from '~/generated-metadata/graphql';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import React from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
import { themeCssVariables } from 'twenty-ui/theme-constants';
|
||||
|
||||
export const StyledTableRow = styled(TableRow)`
|
||||
grid-template-columns: 164px 1fr 96px 32px;
|
||||
`;
|
||||
export const StyledTableRow = (
|
||||
props: React.ComponentProps<typeof TableRow>,
|
||||
) => (
|
||||
<TableRow
|
||||
gridTemplateColumns="164px 1fr 96px 32px"
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
|
||||
const StyledTableBody = styled(TableBody)`
|
||||
const StyledTableBodyContainer = styled.div`
|
||||
border-bottom: 1px solid ${themeCssVariables.border.color.light};
|
||||
`;
|
||||
|
||||
@@ -40,18 +47,20 @@ export const SettingsLogicFunctionsTable = ({
|
||||
<TableHeader>{t`Runtime`}</TableHeader>
|
||||
<TableHeader></TableHeader>
|
||||
</StyledTableRow>
|
||||
<StyledTableBody>
|
||||
{logicFunctions.map((logicFunction: LogicFunction) => (
|
||||
<SettingsLogicFunctionsFieldItemTableRow
|
||||
key={logicFunction.id}
|
||||
logicFunction={logicFunction}
|
||||
to={getSettingsPath(SettingsPath.ApplicationLogicFunctionDetail, {
|
||||
applicationId,
|
||||
logicFunctionId: logicFunction.id,
|
||||
})}
|
||||
/>
|
||||
))}
|
||||
</StyledTableBody>
|
||||
<StyledTableBodyContainer>
|
||||
<TableBody>
|
||||
{logicFunctions.map((logicFunction: LogicFunction) => (
|
||||
<SettingsLogicFunctionsFieldItemTableRow
|
||||
key={logicFunction.id}
|
||||
logicFunction={logicFunction}
|
||||
to={getSettingsPath(SettingsPath.ApplicationLogicFunctionDetail, {
|
||||
applicationId,
|
||||
logicFunctionId: logicFunction.id,
|
||||
})}
|
||||
/>
|
||||
))}
|
||||
</TableBody>
|
||||
</StyledTableBodyContainer>
|
||||
</Table>
|
||||
);
|
||||
};
|
||||
|
||||
+12
-8
@@ -12,8 +12,10 @@ import { H2Title, IconPlayerPlay } from 'twenty-ui/display';
|
||||
import { Button, CoreEditorHeader } from 'twenty-ui/input';
|
||||
import { Section } from 'twenty-ui/layout';
|
||||
|
||||
const StyledTabList = styled(TabList)`
|
||||
border-bottom: none;
|
||||
const StyledTabListContainer = styled.div`
|
||||
> * {
|
||||
border-bottom: none;
|
||||
}
|
||||
`;
|
||||
|
||||
export const SettingsLogicFunctionCodeEditorTab = ({
|
||||
@@ -44,12 +46,14 @@ export const SettingsLogicFunctionCodeEditorTab = ({
|
||||
);
|
||||
|
||||
const HeaderTabList = (
|
||||
<StyledTabList
|
||||
tabs={files.map((file) => {
|
||||
return { id: file.path, title: file.path.split('/').at(-1) || '' };
|
||||
})}
|
||||
componentInstanceId={SETTINGS_LOGIC_FUNCTION_TAB_LIST_COMPONENT_ID}
|
||||
/>
|
||||
<StyledTabListContainer>
|
||||
<TabList
|
||||
tabs={files.map((file) => {
|
||||
return { id: file.path, title: file.path.split('/').at(-1) || '' };
|
||||
})}
|
||||
componentInstanceId={SETTINGS_LOGIC_FUNCTION_TAB_LIST_COMPONENT_ID}
|
||||
/>
|
||||
</StyledTabListContainer>
|
||||
);
|
||||
|
||||
return (
|
||||
|
||||
+17
-8
@@ -5,9 +5,8 @@ import { DropdownMenuItemsContainer } from '@/ui/layout/dropdown/components/Drop
|
||||
import { useCloseDropdown } from '@/ui/layout/dropdown/hooks/useCloseDropdown';
|
||||
import { TableCell } from '@/ui/layout/table/components/TableCell';
|
||||
import { TableRow } from '@/ui/layout/table/components/TableRow';
|
||||
import { styled } from '@linaria/react';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { useState } from 'react';
|
||||
import React, { useState } from 'react';
|
||||
import {
|
||||
IconCheck,
|
||||
IconDotsVertical,
|
||||
@@ -20,13 +19,23 @@ import { LightIconButton } from 'twenty-ui/input';
|
||||
import { MenuItem } from 'twenty-ui/navigation';
|
||||
import type { ApplicationVariable } from '~/generated-metadata/graphql';
|
||||
|
||||
const StyledEditModeTableRow = styled(TableRow)`
|
||||
grid-template-columns: 180px auto 56px;
|
||||
`;
|
||||
const StyledEditModeTableRow = (
|
||||
props: React.ComponentProps<typeof TableRow>,
|
||||
) => (
|
||||
<TableRow
|
||||
gridTemplateColumns="180px auto 56px"
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
|
||||
const StyledTableRow = styled(TableRow)`
|
||||
grid-template-columns: 180px 300px 32px;
|
||||
`;
|
||||
const StyledTableRow = (props: React.ComponentProps<typeof TableRow>) => (
|
||||
<TableRow
|
||||
gridTemplateColumns="180px 300px 32px"
|
||||
// eslint-disable-next-line react/jsx-props-no-spreading
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
|
||||
export const SettingsLogicFunctionTabEnvironmentVariableTableRow = ({
|
||||
envVariable,
|
||||
|
||||
+32
-23
@@ -14,18 +14,9 @@ import { Tag } from 'twenty-ui/components';
|
||||
import { type LogicFunction } from '~/generated-metadata/graphql';
|
||||
import { themeCssVariables } from 'twenty-ui/theme-constants';
|
||||
|
||||
export const StyledRouteTriggerTableRow = styled(TableRow)`
|
||||
grid-template-columns: 1fr 120px 120px;
|
||||
`;
|
||||
const ROUTE_TRIGGER_GRID_TEMPLATE_COLUMNS = '1fr 120px 120px';
|
||||
|
||||
const StyledTableCell = styled(TableCell)`
|
||||
color: ${themeCssVariables.font.color.tertiary};
|
||||
gap: ${themeCssVariables.spacing[2]};
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
`;
|
||||
|
||||
const StyledRouteTriggerTableHeaderRow = styled(StyledRouteTriggerTableRow)`
|
||||
const StyledRouteTriggerTableHeaderRowWrapper = styled.div`
|
||||
margin-bottom: ${themeCssVariables.spacing[2]};
|
||||
`;
|
||||
|
||||
@@ -119,26 +110,44 @@ export const SettingsLogicFunctionTriggersTab = ({
|
||||
description={t`Triggers the function with Http request`}
|
||||
/>
|
||||
<Table>
|
||||
<StyledRouteTriggerTableHeaderRow>
|
||||
<TableHeader>{t`Path`}</TableHeader>
|
||||
<TableHeader>{t`Method`}</TableHeader>
|
||||
<TableHeader>{t`Auth Required`}</TableHeader>
|
||||
</StyledRouteTriggerTableHeaderRow>
|
||||
<StyledRouteTriggerTableRow>
|
||||
<StyledTableCell>
|
||||
<StyledRouteTriggerTableHeaderRowWrapper>
|
||||
<TableRow
|
||||
gridTemplateColumns={ROUTE_TRIGGER_GRID_TEMPLATE_COLUMNS}
|
||||
>
|
||||
<TableHeader>{t`Path`}</TableHeader>
|
||||
<TableHeader>{t`Method`}</TableHeader>
|
||||
<TableHeader>{t`Auth Required`}</TableHeader>
|
||||
</TableRow>
|
||||
</StyledRouteTriggerTableHeaderRowWrapper>
|
||||
<TableRow gridTemplateColumns={ROUTE_TRIGGER_GRID_TEMPLATE_COLUMNS}>
|
||||
<TableCell
|
||||
color={themeCssVariables.font.color.tertiary}
|
||||
gap={themeCssVariables.spacing[2]}
|
||||
overflow="hidden"
|
||||
>
|
||||
<OverflowingTextWithTooltip
|
||||
text={`${REACT_APP_SERVER_BASE_URL}/s${routeTrigger.path}`}
|
||||
/>
|
||||
</StyledTableCell>
|
||||
<StyledTableCell>{routeTrigger.httpMethod}</StyledTableCell>
|
||||
<StyledTableCell>
|
||||
</TableCell>
|
||||
<TableCell
|
||||
color={themeCssVariables.font.color.tertiary}
|
||||
gap={themeCssVariables.spacing[2]}
|
||||
overflow="hidden"
|
||||
>
|
||||
{routeTrigger.httpMethod}
|
||||
</TableCell>
|
||||
<TableCell
|
||||
color={themeCssVariables.font.color.tertiary}
|
||||
gap={themeCssVariables.spacing[2]}
|
||||
overflow="hidden"
|
||||
>
|
||||
<Tag
|
||||
text={routeTrigger.isAuthRequired ? t`True` : t`False`}
|
||||
color={routeTrigger.isAuthRequired ? 'green' : 'orange'}
|
||||
weight="medium"
|
||||
/>
|
||||
</StyledTableCell>
|
||||
</StyledRouteTriggerTableRow>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</Table>
|
||||
</Section>
|
||||
)}
|
||||
|
||||
+4
-4
@@ -3,9 +3,9 @@ import { type ReactNode, useContext } from 'react';
|
||||
|
||||
import DarkCoverImage from '@/settings/playground/assets/cover-dark.png';
|
||||
import LightCoverImage from '@/settings/playground/assets/cover-light.png';
|
||||
import { Card } from 'twenty-ui/layout';
|
||||
import { ThemeContext, themeCssVariables } from 'twenty-ui/theme-constants';
|
||||
const StyledCard = styled(Card)`
|
||||
|
||||
const StyledCoverContainer = styled.div`
|
||||
align-items: center;
|
||||
background-size: cover;
|
||||
border-radius: ${themeCssVariables.border.radius.md};
|
||||
@@ -34,11 +34,11 @@ export const StyledSettingsApiPlaygroundCoverImage = ({
|
||||
? LightCoverImage.toString()
|
||||
: DarkCoverImage.toString();
|
||||
return (
|
||||
<StyledCard
|
||||
<StyledCoverContainer
|
||||
className={className}
|
||||
style={{ backgroundImage: `url('${coverImage}')` }}
|
||||
>
|
||||
{children}
|
||||
</StyledCard>
|
||||
</StyledCoverContainer>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -31,7 +31,7 @@ const StyledActionWrapper = styled.div`
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledActionButton = styled(Button)`
|
||||
const StyledActionButtonContainer = styled.div`
|
||||
height: 100%;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
@@ -96,34 +96,40 @@ export const EmailField = () => {
|
||||
/>
|
||||
{isEditing ? (
|
||||
<StyledActionWrapper key="editing">
|
||||
<StyledActionButton
|
||||
Icon={IconCheck}
|
||||
variant="secondary"
|
||||
position="left"
|
||||
size="small"
|
||||
onClick={handleSave}
|
||||
disabled={isSaveDisabled}
|
||||
type="button"
|
||||
/>
|
||||
<StyledActionButton
|
||||
Icon={IconX}
|
||||
variant="secondary"
|
||||
position="right"
|
||||
size="small"
|
||||
onClick={handleCancelEditing}
|
||||
type="button"
|
||||
/>
|
||||
<StyledActionButtonContainer>
|
||||
<Button
|
||||
Icon={IconCheck}
|
||||
variant="secondary"
|
||||
position="left"
|
||||
size="small"
|
||||
onClick={handleSave}
|
||||
disabled={isSaveDisabled}
|
||||
type="button"
|
||||
/>
|
||||
</StyledActionButtonContainer>
|
||||
<StyledActionButtonContainer>
|
||||
<Button
|
||||
Icon={IconX}
|
||||
variant="secondary"
|
||||
position="right"
|
||||
size="small"
|
||||
onClick={handleCancelEditing}
|
||||
type="button"
|
||||
/>
|
||||
</StyledActionButtonContainer>
|
||||
</StyledActionWrapper>
|
||||
) : (
|
||||
<StyledActionWrapper key="view">
|
||||
<StyledActionButton
|
||||
Icon={IconPencil}
|
||||
variant="secondary"
|
||||
size="small"
|
||||
onClick={handleStartEditing}
|
||||
disabled={!canEdit}
|
||||
type="button"
|
||||
/>
|
||||
<StyledActionButtonContainer>
|
||||
<Button
|
||||
Icon={IconPencil}
|
||||
variant="secondary"
|
||||
size="small"
|
||||
onClick={handleStartEditing}
|
||||
disabled={!canEdit}
|
||||
type="button"
|
||||
/>
|
||||
</StyledActionButtonContainer>
|
||||
</StyledActionWrapper>
|
||||
)}
|
||||
</StyledFieldRow>
|
||||
|
||||
@@ -27,12 +27,14 @@ import { themeCssVariables } from 'twenty-ui/theme-constants';
|
||||
import { useNavigateSettings } from '~/hooks/useNavigateSettings';
|
||||
import { sortByAscString } from '~/utils/array/sortByAscString';
|
||||
|
||||
const StyledCreateRoleSection = styled(Section)`
|
||||
border-top: 1px solid ${themeCssVariables.border.color.light};
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
padding-top: ${themeCssVariables.spacing[2]};
|
||||
padding-bottom: ${themeCssVariables.spacing[2]};
|
||||
const StyledCreateRoleSectionContainer = styled.div`
|
||||
> * {
|
||||
border-top: 1px solid ${themeCssVariables.border.color.light};
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
padding-top: ${themeCssVariables.spacing[2]};
|
||||
padding-bottom: ${themeCssVariables.spacing[2]};
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledTableRows = styled.div`
|
||||
@@ -40,10 +42,6 @@ const StyledTableRows = styled.div`
|
||||
padding-top: ${themeCssVariables.spacing[2]};
|
||||
`;
|
||||
|
||||
const StyledNoRoles = styled(TableCell)`
|
||||
color: ${themeCssVariables.font.color.tertiary};
|
||||
`;
|
||||
|
||||
const StyledSearchAndFilterContainer = styled.div`
|
||||
display: flex;
|
||||
gap: ${themeCssVariables.spacing[2]};
|
||||
@@ -51,7 +49,7 @@ const StyledSearchAndFilterContainer = styled.div`
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
const StyledSearchInput = styled(SettingsTextInput)`
|
||||
const StyledSearchInputContainer = styled.div`
|
||||
flex: 1;
|
||||
`;
|
||||
|
||||
@@ -88,13 +86,15 @@ export const SettingsRolesList = () => {
|
||||
/>
|
||||
|
||||
<StyledSearchAndFilterContainer>
|
||||
<StyledSearchInput
|
||||
instanceId="settings-roles-search"
|
||||
LeftIcon={IconSearch}
|
||||
placeholder={t`Search a role...`}
|
||||
value={searchTerm}
|
||||
onChange={setSearchTerm}
|
||||
/>
|
||||
<StyledSearchInputContainer>
|
||||
<SettingsTextInput
|
||||
instanceId="settings-roles-search"
|
||||
LeftIcon={IconSearch}
|
||||
placeholder={t`Search a role...`}
|
||||
value={searchTerm}
|
||||
onChange={setSearchTerm}
|
||||
/>
|
||||
</StyledSearchInputContainer>
|
||||
<Dropdown
|
||||
dropdownId="settings-roles-filter-dropdown"
|
||||
dropdownPlacement="bottom-end"
|
||||
@@ -135,7 +135,9 @@ export const SettingsRolesList = () => {
|
||||
<SettingsRolesTableHeader />
|
||||
<StyledTableRows>
|
||||
{filteredRoles.length === 0 ? (
|
||||
<StyledNoRoles>{t`No roles found`}</StyledNoRoles>
|
||||
<TableCell color={themeCssVariables.font.color.tertiary}>
|
||||
{t`No roles found`}
|
||||
</TableCell>
|
||||
) : (
|
||||
filteredRoles.map((role) => (
|
||||
<SettingsRolesTableRow key={role.id} role={role} />
|
||||
@@ -143,15 +145,17 @@ export const SettingsRolesList = () => {
|
||||
)}
|
||||
</StyledTableRows>
|
||||
</Table>
|
||||
<StyledCreateRoleSection>
|
||||
<Button
|
||||
Icon={IconPlus}
|
||||
title={t`Create Role`}
|
||||
variant="secondary"
|
||||
size="small"
|
||||
onClick={() => navigateSettings(SettingsPath.RoleCreate)}
|
||||
/>
|
||||
</StyledCreateRoleSection>
|
||||
<StyledCreateRoleSectionContainer>
|
||||
<Section>
|
||||
<Button
|
||||
Icon={IconPlus}
|
||||
title={t`Create Role`}
|
||||
variant="secondary"
|
||||
size="small"
|
||||
onClick={() => navigateSettings(SettingsPath.RoleCreate)}
|
||||
/>
|
||||
</Section>
|
||||
</StyledCreateRoleSectionContainer>
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
|
||||
+61
-55
@@ -48,10 +48,12 @@ const StyledIconLockContainer = styled.div`
|
||||
justify-content: flex-end;
|
||||
`;
|
||||
|
||||
const StyledTableRow = styled(TableRow)`
|
||||
&:hover {
|
||||
background: ${themeCssVariables.background.transparent.light};
|
||||
cursor: pointer;
|
||||
const StyledTableRowContainer = styled.div`
|
||||
> * {
|
||||
&:hover {
|
||||
background: ${themeCssVariables.background.transparent.light};
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
@@ -77,57 +79,61 @@ export const SettingsRolesTableRow = ({ role }: SettingsRolesTableRowProps) => {
|
||||
.filter(isDefined);
|
||||
|
||||
return (
|
||||
<StyledTableRow
|
||||
key={role.id}
|
||||
gridAutoColumns="332px 3fr 2fr 1fr"
|
||||
to={getSettingsPath(SettingsPath.RoleDetail, { roleId: role.id })}
|
||||
>
|
||||
<TableCell>
|
||||
<StyledNameCell>
|
||||
<Icon size={theme.icon.size.md} stroke={theme.icon.stroke.sm} />
|
||||
{role.label}
|
||||
{!role.isEditable && (
|
||||
<StyledIconLockContainer>
|
||||
<IconLock
|
||||
color={theme.font.color.light}
|
||||
stroke={theme.icon.stroke.sm}
|
||||
size={theme.icon.size.sm}
|
||||
/>
|
||||
</StyledIconLockContainer>
|
||||
)}
|
||||
</StyledNameCell>
|
||||
</TableCell>
|
||||
<TableCell align="right">
|
||||
<StyledAvatarGroup>
|
||||
{enrichedWorkspaceMembers.slice(0, 5).map((workspaceMember) => (
|
||||
<React.Fragment key={workspaceMember.id}>
|
||||
<div id={`avatar-${workspaceMember.id}`}>
|
||||
<Avatar
|
||||
avatarUrl={workspaceMember.avatarUrl}
|
||||
placeholderColorSeed={workspaceMember.id}
|
||||
placeholder={workspaceMember.name.firstName ?? ''}
|
||||
type="rounded"
|
||||
size="md"
|
||||
<StyledTableRowContainer>
|
||||
<TableRow
|
||||
key={role.id}
|
||||
gridAutoColumns="332px 3fr 2fr 1fr"
|
||||
to={getSettingsPath(SettingsPath.RoleDetail, { roleId: role.id })}
|
||||
>
|
||||
<TableCell>
|
||||
<StyledNameCell>
|
||||
<Icon size={theme.icon.size.md} stroke={theme.icon.stroke.sm} />
|
||||
{role.label}
|
||||
{!role.isEditable && (
|
||||
<StyledIconLockContainer>
|
||||
<IconLock
|
||||
color={theme.font.color.light}
|
||||
stroke={theme.icon.stroke.sm}
|
||||
size={theme.icon.size.sm}
|
||||
/>
|
||||
</div>
|
||||
<AppTooltip
|
||||
anchorSelect={`#avatar-${workspaceMember.id}`}
|
||||
content={`${workspaceMember.name.firstName} ${workspaceMember.name.lastName}`}
|
||||
noArrow
|
||||
place="top"
|
||||
positionStrategy="fixed"
|
||||
delay={TooltipDelay.shortDelay}
|
||||
/>
|
||||
</React.Fragment>
|
||||
))}
|
||||
</StyledAvatarGroup>
|
||||
</TableCell>
|
||||
<TableCell align="left">
|
||||
<StyledAssignedText>{role.workspaceMembers.length}</StyledAssignedText>
|
||||
</TableCell>
|
||||
<TableCell align="right" color={theme.font.color.tertiary}>
|
||||
<IconChevronRight size={theme.icon.size.md} />
|
||||
</TableCell>
|
||||
</StyledTableRow>
|
||||
</StyledIconLockContainer>
|
||||
)}
|
||||
</StyledNameCell>
|
||||
</TableCell>
|
||||
<TableCell align="right">
|
||||
<StyledAvatarGroup>
|
||||
{enrichedWorkspaceMembers.slice(0, 5).map((workspaceMember) => (
|
||||
<React.Fragment key={workspaceMember.id}>
|
||||
<div id={`avatar-${workspaceMember.id}`}>
|
||||
<Avatar
|
||||
avatarUrl={workspaceMember.avatarUrl}
|
||||
placeholderColorSeed={workspaceMember.id}
|
||||
placeholder={workspaceMember.name.firstName ?? ''}
|
||||
type="rounded"
|
||||
size="md"
|
||||
/>
|
||||
</div>
|
||||
<AppTooltip
|
||||
anchorSelect={`#avatar-${workspaceMember.id}`}
|
||||
content={`${workspaceMember.name.firstName} ${workspaceMember.name.lastName}`}
|
||||
noArrow
|
||||
place="top"
|
||||
positionStrategy="fixed"
|
||||
delay={TooltipDelay.shortDelay}
|
||||
/>
|
||||
</React.Fragment>
|
||||
))}
|
||||
</StyledAvatarGroup>
|
||||
</TableCell>
|
||||
<TableCell align="left">
|
||||
<StyledAssignedText>
|
||||
{role.workspaceMembers.length}
|
||||
</StyledAssignedText>
|
||||
</TableCell>
|
||||
<TableCell align="right" color={theme.font.color.tertiary}>
|
||||
<IconChevronRight size={theme.icon.size.md} />
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</StyledTableRowContainer>
|
||||
);
|
||||
};
|
||||
|
||||
+15
-17
@@ -27,16 +27,12 @@ const StyledTableRows = styled.div`
|
||||
padding-block: ${themeCssVariables.spacing[2]};
|
||||
`;
|
||||
|
||||
const StyledEmptyState = styled(TableCell)`
|
||||
color: ${themeCssVariables.font.color.tertiary};
|
||||
`;
|
||||
|
||||
const StyledSearchContainer = styled.div`
|
||||
padding-bottom: ${themeCssVariables.spacing[2]};
|
||||
`;
|
||||
|
||||
const StyledSearchInput = styled(SettingsTextInput)`
|
||||
input {
|
||||
const StyledSearchInputContainer = styled.div`
|
||||
> * input {
|
||||
background: ${themeCssVariables.background.transparent.lighter};
|
||||
border: 1px solid ${themeCssVariables.border.color.medium};
|
||||
}
|
||||
@@ -148,15 +144,17 @@ export const SettingsRoleAssignmentTable = <T extends RoleTargetType>({
|
||||
description={t`This role is assigned to these ${roleTargetDisplayName}.`}
|
||||
/>
|
||||
<StyledSearchContainer>
|
||||
<StyledSearchInput
|
||||
instanceId={`role-assignment-${roleTargetType}-search`}
|
||||
value={searchFilter}
|
||||
onChange={handleSearchChange}
|
||||
placeholder={t`Search an assigned ${roleTargetDisplayName}...`}
|
||||
fullWidth
|
||||
LeftIcon={IconSearch}
|
||||
sizeVariant="lg"
|
||||
/>
|
||||
<StyledSearchInputContainer>
|
||||
<SettingsTextInput
|
||||
instanceId={`role-assignment-${roleTargetType}-search`}
|
||||
value={searchFilter}
|
||||
onChange={handleSearchChange}
|
||||
placeholder={t`Search an assigned ${roleTargetDisplayName}...`}
|
||||
fullWidth
|
||||
LeftIcon={IconSearch}
|
||||
sizeVariant="lg"
|
||||
/>
|
||||
</StyledSearchInputContainer>
|
||||
</StyledSearchContainer>
|
||||
<StyledTable>
|
||||
<TableRow gridAutoColumns="2fr 4fr">
|
||||
@@ -173,9 +171,9 @@ export const SettingsRoleAssignmentTable = <T extends RoleTargetType>({
|
||||
))}
|
||||
|
||||
{filteredRoleTargets.length === 0 && (
|
||||
<StyledEmptyState>
|
||||
<TableCell color={themeCssVariables.font.color.tertiary}>
|
||||
{tableConfig[roleTargetType].emptyStateText}
|
||||
</StyledEmptyState>
|
||||
</TableCell>
|
||||
)}
|
||||
</StyledTableRows>
|
||||
</StyledTable>
|
||||
|
||||
+4
-8
@@ -38,10 +38,6 @@ const StyledNameContainer = styled.div`
|
||||
width: 100%;
|
||||
`;
|
||||
|
||||
const StyledTableCell = styled(TableCell)`
|
||||
overflow: hidden;
|
||||
`;
|
||||
|
||||
export type RoleTarget =
|
||||
| { type: 'member'; data: PartialWorkspaceMember }
|
||||
| { type: 'agent'; data: Agent }
|
||||
@@ -119,17 +115,17 @@ export const SettingsRoleAssignmentTableRow = ({
|
||||
|
||||
return (
|
||||
<TableRow gridAutoColumns="2fr 4fr">
|
||||
<StyledTableCell>
|
||||
<TableCell overflow="hidden">
|
||||
<StyledNameContainer>
|
||||
<StyledIconWrapper>{renderIcon()}</StyledIconWrapper>
|
||||
<StyledNameCell>
|
||||
<OverflowingTextWithTooltip text={renderName()} />
|
||||
</StyledNameCell>
|
||||
</StyledNameContainer>
|
||||
</StyledTableCell>
|
||||
<StyledTableCell>
|
||||
</TableCell>
|
||||
<TableCell overflow="hidden">
|
||||
<OverflowingTextWithTooltip text={renderSecondaryInfo()} />
|
||||
</StyledTableCell>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
};
|
||||
|
||||
+13
-11
@@ -39,8 +39,8 @@ const StyledSearchContainer = styled.div`
|
||||
padding-bottom: ${themeCssVariables.spacing[2]};
|
||||
`;
|
||||
|
||||
const StyledSearchInput = styled(SettingsTextInput)`
|
||||
input {
|
||||
const StyledSearchInputContainer = styled.div`
|
||||
> * input {
|
||||
background: ${themeCssVariables.background.transparent.lighter};
|
||||
border: 1px solid ${themeCssVariables.border.color.medium};
|
||||
}
|
||||
@@ -104,15 +104,17 @@ export const SettingsRolePermissionsObjectLevelObjectPicker = ({
|
||||
<StyledTypeSelectContainer>
|
||||
<Section>
|
||||
<StyledSearchContainer>
|
||||
<StyledSearchInput
|
||||
instanceId="role-permissions-object-search"
|
||||
value={searchFilter}
|
||||
onChange={handleSearchChange}
|
||||
placeholder={t`Search an object`}
|
||||
fullWidth
|
||||
LeftIcon={IconSearch}
|
||||
sizeVariant="lg"
|
||||
/>
|
||||
<StyledSearchInputContainer>
|
||||
<SettingsTextInput
|
||||
instanceId="role-permissions-object-search"
|
||||
value={searchFilter}
|
||||
onChange={handleSearchChange}
|
||||
placeholder={t`Search an object`}
|
||||
fullWidth
|
||||
LeftIcon={IconSearch}
|
||||
sizeVariant="lg"
|
||||
/>
|
||||
</StyledSearchInputContainer>
|
||||
</StyledSearchContainer>
|
||||
</Section>
|
||||
|
||||
|
||||
+24
-24
@@ -18,12 +18,14 @@ import { Section } from 'twenty-ui/layout';
|
||||
import { themeCssVariables } from 'twenty-ui/theme-constants';
|
||||
import { useNavigateSettings } from '~/hooks/useNavigateSettings';
|
||||
|
||||
const StyledCreateObjectOverrideSection = styled(Section)`
|
||||
border-top: 1px solid ${themeCssVariables.border.color.light};
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
padding-top: ${themeCssVariables.spacing[2]};
|
||||
padding-bottom: ${themeCssVariables.spacing[2]};
|
||||
const StyledCreateObjectOverrideSectionContainer = styled.div`
|
||||
> * {
|
||||
border-top: 1px solid ${themeCssVariables.border.color.light};
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
padding-top: ${themeCssVariables.spacing[2]};
|
||||
padding-bottom: ${themeCssVariables.spacing[2]};
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledTableRows = styled.div`
|
||||
@@ -38,10 +40,6 @@ type SettingsRolePermissionsObjectLevelSectionProps = {
|
||||
objectMetadataItemsFromMarketplaceApp?: ObjectMetadataItem[];
|
||||
};
|
||||
|
||||
const StyledNoOverride = styled(TableCell)`
|
||||
color: ${themeCssVariables.font.color.tertiary};
|
||||
`;
|
||||
|
||||
export const SettingsRolePermissionsObjectLevelSection = ({
|
||||
roleId,
|
||||
fromAgentId,
|
||||
@@ -130,25 +128,27 @@ export const SettingsRolePermissionsObjectLevelSection = ({
|
||||
),
|
||||
)
|
||||
) : (
|
||||
<StyledNoOverride>
|
||||
<TableCell color={themeCssVariables.font.color.tertiary}>
|
||||
{t`No permissions have been set for individual objects.`}
|
||||
</StyledNoOverride>
|
||||
</TableCell>
|
||||
)}
|
||||
</StyledTableRows>
|
||||
</Table>
|
||||
{isEditable && (
|
||||
<StyledCreateObjectOverrideSection>
|
||||
<Button
|
||||
Icon={IconPlus}
|
||||
title={t`Add rule`}
|
||||
variant="secondary"
|
||||
size="small"
|
||||
disabled={
|
||||
!settingsDraftRole.isEditable || allObjectsHaveSetPermission
|
||||
}
|
||||
onClick={handleAddRule}
|
||||
/>
|
||||
</StyledCreateObjectOverrideSection>
|
||||
<StyledCreateObjectOverrideSectionContainer>
|
||||
<Section>
|
||||
<Button
|
||||
Icon={IconPlus}
|
||||
title={t`Add rule`}
|
||||
variant="secondary"
|
||||
size="small"
|
||||
disabled={
|
||||
!settingsDraftRole.isEditable || allObjectsHaveSetPermission
|
||||
}
|
||||
onClick={handleAddRule}
|
||||
/>
|
||||
</Section>
|
||||
</StyledCreateObjectOverrideSectionContainer>
|
||||
)}
|
||||
</Section>
|
||||
);
|
||||
|
||||
+10
-14
@@ -13,22 +13,12 @@ import { OverflowingTextWithTooltip, useIcons } from 'twenty-ui/display';
|
||||
import { useContext } from 'react';
|
||||
import { ThemeContext, themeCssVariables } from 'twenty-ui/theme-constants';
|
||||
|
||||
const StyledNameTableCell = styled(TableCell)`
|
||||
color: ${themeCssVariables.font.color.primary};
|
||||
gap: ${themeCssVariables.spacing[1]};
|
||||
`;
|
||||
|
||||
const StyledNameLabel = styled.div`
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
`;
|
||||
|
||||
const StyledOptionsTableCell = styled(TableCell)`
|
||||
justify-content: flex-end;
|
||||
padding-right: ${themeCssVariables.spacing[1]};
|
||||
`;
|
||||
|
||||
type SettingsRolePermissionsObjectLevelTableRowProps = {
|
||||
objectMetadataItem: ObjectMetadataItem;
|
||||
roleId: string;
|
||||
@@ -62,7 +52,10 @@ export const SettingsRolePermissionsObjectLevelTableRow = ({
|
||||
to={isEditable ? navigationUrl : undefined}
|
||||
gridAutoColumns={OBJECT_LEVEL_PERMISSION_TABLE_GRID_AUTO_COLUMNS}
|
||||
>
|
||||
<StyledNameTableCell>
|
||||
<TableCell
|
||||
color={themeCssVariables.font.color.primary}
|
||||
gap={themeCssVariables.spacing[1]}
|
||||
>
|
||||
{!!Icon && (
|
||||
<Icon
|
||||
style={{
|
||||
@@ -75,7 +68,7 @@ export const SettingsRolePermissionsObjectLevelTableRow = ({
|
||||
<StyledNameLabel title={objectLabelPlural}>
|
||||
<OverflowingTextWithTooltip text={objectLabelPlural} />
|
||||
</StyledNameLabel>
|
||||
</StyledNameTableCell>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<SettingsRolePermissionsObjectLevelOverrideCellContainer
|
||||
objectMetadataItem={objectMetadataItem}
|
||||
@@ -96,14 +89,17 @@ export const SettingsRolePermissionsObjectLevelTableRow = ({
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell></TableCell>
|
||||
<StyledOptionsTableCell>
|
||||
<TableCell
|
||||
align="right"
|
||||
padding={`0 ${themeCssVariables.spacing[1]} 0 ${themeCssVariables.spacing[2]}`}
|
||||
>
|
||||
<SettingsRolePermissionsObjectLevelTableRowOptionsDropdown
|
||||
roleId={roleId}
|
||||
objectMetadataId={objectMetadataItem.id}
|
||||
objectPermissionDetailUrl={navigationUrl}
|
||||
isEditable={isEditable}
|
||||
/>
|
||||
</StyledOptionsTableCell>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
};
|
||||
|
||||
+16
-11
@@ -1,9 +1,10 @@
|
||||
import { type ObjectMetadataItem } from '@/object-metadata/types/ObjectMetadataItem';
|
||||
import { filterUserFacingFieldMetadataItems } from '@/object-metadata/utils/filterUserFacingFieldMetadataItems';
|
||||
import { SettingsRolePermissionsObjectLevelObjectFieldPermissionTableAllHeaderRow } from '@/settings/roles/role-permissions/object-level-permissions/field-permissions/components/SettingsRolePermissionsObjectLevelObjectFieldPermissionTableAllHeaderRow';
|
||||
import { TableRow } from '@/ui/layout/table/components/TableRow';
|
||||
import {
|
||||
FIELD_PERMISSION_TABLE_ROW_GRID_TEMPLATE_COLUMNS,
|
||||
SettingsRolePermissionsObjectLevelObjectFieldPermissionTableRow,
|
||||
StyledObjectFieldTableRow,
|
||||
} from '@/settings/roles/role-permissions/object-level-permissions/field-permissions/components/SettingsRolePermissionsObjectLevelObjectFieldPermissionTableRow';
|
||||
import { useObjectPermissionDerivedStates } from '@/settings/roles/role-permissions/object-level-permissions/field-permissions/hooks/useObjectPermissionDerivedStates';
|
||||
import { settingsDraftRoleFamilyState } from '@/settings/roles/states/settingsDraftRoleFamilyState';
|
||||
@@ -28,7 +29,7 @@ import { turnOrderByIntoSort } from '~/utils/turnOrderByIntoSort';
|
||||
export const SETTINGS_ROLE_PERMISSION_OBJECT_LEVEL_FIELD_PERMISSION_TABLE_ID =
|
||||
'settings-role-permissions-object-level-object-field-permission';
|
||||
|
||||
const StyledSearchInput = styled(SettingsTextInput)`
|
||||
const StyledSearchInputContainer = styled.div`
|
||||
padding-bottom: ${themeCssVariables.spacing[2]};
|
||||
width: 100%;
|
||||
`;
|
||||
@@ -99,15 +100,19 @@ export const SettingsRolePermissionsObjectLevelObjectFieldPermissionTable = ({
|
||||
title={t`Fields Permissions`}
|
||||
description={t`Ability to interact with this object's fields.`}
|
||||
/>
|
||||
<StyledSearchInput
|
||||
instanceId="object-field-table-search"
|
||||
LeftIcon={IconSearch}
|
||||
placeholder={t`Search a field...`}
|
||||
value={searchTerm}
|
||||
onChange={setSearchTerm}
|
||||
/>
|
||||
<StyledSearchInputContainer>
|
||||
<SettingsTextInput
|
||||
instanceId="object-field-table-search"
|
||||
LeftIcon={IconSearch}
|
||||
placeholder={t`Search a field...`}
|
||||
value={searchTerm}
|
||||
onChange={setSearchTerm}
|
||||
/>
|
||||
</StyledSearchInputContainer>
|
||||
<Table>
|
||||
<StyledObjectFieldTableRow>
|
||||
<TableRow
|
||||
gridTemplateColumns={FIELD_PERMISSION_TABLE_ROW_GRID_TEMPLATE_COLUMNS}
|
||||
>
|
||||
<SortableTableHeader
|
||||
fieldName="label"
|
||||
label={t`Name`}
|
||||
@@ -132,7 +137,7 @@ export const SettingsRolePermissionsObjectLevelObjectFieldPermissionTable = ({
|
||||
</TableHeader>
|
||||
)}
|
||||
</>
|
||||
</StyledObjectFieldTableRow>
|
||||
</TableRow>
|
||||
<SettingsRolePermissionsObjectLevelObjectFieldPermissionTableAllHeaderRow
|
||||
roleId={roleId}
|
||||
objectMetadataItem={objectMetadataItem}
|
||||
|
||||
+11
-12
@@ -20,14 +20,8 @@ import {
|
||||
RelationType,
|
||||
} from '~/generated-metadata/graphql';
|
||||
|
||||
export const StyledObjectFieldTableRow = styled(TableRow)`
|
||||
grid-template-columns: 180px minmax(0, 1fr) 60px 60px;
|
||||
`;
|
||||
|
||||
const StyledNameTableCell = styled(TableCell)`
|
||||
color: ${themeCssVariables.font.color.primary};
|
||||
gap: ${themeCssVariables.spacing[2]};
|
||||
`;
|
||||
export const FIELD_PERMISSION_TABLE_ROW_GRID_TEMPLATE_COLUMNS =
|
||||
'180px minmax(0, 1fr) 60px 60px';
|
||||
|
||||
const StyledNameLabel = styled.div`
|
||||
white-space: nowrap;
|
||||
@@ -156,8 +150,13 @@ export const SettingsRolePermissionsObjectLevelObjectFieldPermissionTableRow =
|
||||
const shouldShowEmptyTableHeader = cannotAllowFieldUpdateRestrict;
|
||||
|
||||
return (
|
||||
<StyledObjectFieldTableRow>
|
||||
<StyledNameTableCell>
|
||||
<TableRow
|
||||
gridTemplateColumns={FIELD_PERMISSION_TABLE_ROW_GRID_TEMPLATE_COLUMNS}
|
||||
>
|
||||
<TableCell
|
||||
color={themeCssVariables.font.color.primary}
|
||||
gap={themeCssVariables.spacing[2]}
|
||||
>
|
||||
{!!Icon && (
|
||||
<Icon
|
||||
style={{
|
||||
@@ -170,7 +169,7 @@ export const SettingsRolePermissionsObjectLevelObjectFieldPermissionTableRow =
|
||||
<StyledNameLabel title={fieldMetadataItem.label}>
|
||||
{fieldMetadataItem.label}
|
||||
</StyledNameLabel>
|
||||
</StyledNameTableCell>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<SettingsObjectFieldDataType
|
||||
Icon={RelationIcon}
|
||||
@@ -212,6 +211,6 @@ export const SettingsRolePermissionsObjectLevelObjectFieldPermissionTableRow =
|
||||
</TableCell>
|
||||
)}
|
||||
</>
|
||||
</StyledObjectFieldTableRow>
|
||||
</TableRow>
|
||||
);
|
||||
};
|
||||
|
||||
+13
-27
@@ -15,20 +15,6 @@ import { isDefined } from 'twenty-shared/utils';
|
||||
import { themeCssVariables } from 'twenty-ui/theme-constants';
|
||||
import { type ObjectPermission, type Role } from '~/generated-metadata/graphql';
|
||||
|
||||
const StyledTableRow = styled(TableRow)<{ isDisabled: boolean }>`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
cursor: ${({ isDisabled }) => (isDisabled ? 'default' : 'pointer')};
|
||||
`;
|
||||
|
||||
const StyledPermissionCell = styled(TableCell)`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex: 1;
|
||||
gap: ${themeCssVariables.spacing[1]};
|
||||
padding-left: ${themeCssVariables.spacing[2]};
|
||||
`;
|
||||
|
||||
const StyledPermissionContent = styled.div`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
@@ -46,13 +32,6 @@ const StyledOverrideInfo = styled.div`
|
||||
gap: ${themeCssVariables.spacing[1]};
|
||||
`;
|
||||
|
||||
const StyledCheckboxCell = styled(TableCell)`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
padding-right: ${themeCssVariables.spacing[1]};
|
||||
`;
|
||||
|
||||
type OverridableCheckboxType = 'no_cta' | 'default' | 'override';
|
||||
|
||||
type SettingsRolePermissionsObjectLevelObjectFormObjectLevelTableRowProps = {
|
||||
@@ -146,8 +125,11 @@ export const SettingsRolePermissionsObjectLevelObjectFormObjectLevelTableRow =
|
||||
);
|
||||
|
||||
return (
|
||||
<StyledTableRow onClick={handleCheckboxChange} isDisabled={!isEditable}>
|
||||
<StyledPermissionCell>
|
||||
<TableRow
|
||||
onClick={handleCheckboxChange}
|
||||
cursor={!isEditable ? 'default' : 'pointer'}
|
||||
>
|
||||
<TableCell gap={themeCssVariables.spacing[1]}>
|
||||
<StyledPermissionContent>
|
||||
<PermissionIcon
|
||||
permission={permission.key as SettingsRoleObjectPermissionKey}
|
||||
@@ -173,15 +155,19 @@ export const SettingsRolePermissionsObjectLevelObjectFormObjectLevelTableRow =
|
||||
</>
|
||||
) : null}
|
||||
</StyledOverrideInfo>
|
||||
</StyledPermissionCell>
|
||||
<StyledCheckboxCell onClick={(e) => e.stopPropagation()}>
|
||||
</TableCell>
|
||||
<TableCell
|
||||
align="right"
|
||||
padding={`0 ${themeCssVariables.spacing[1]} 0 ${themeCssVariables.spacing[2]}`}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<OverridableCheckbox
|
||||
onChange={handleCheckboxChange}
|
||||
disabled={!isEditable}
|
||||
type={checkboxType}
|
||||
checked={isChecked}
|
||||
/>
|
||||
</StyledCheckboxCell>
|
||||
</StyledTableRow>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
};
|
||||
|
||||
+34
-25
@@ -2,7 +2,6 @@
|
||||
|
||||
import { styled } from '@linaria/react';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { Pill } from 'twenty-ui/components';
|
||||
import { H2Title, IconArrowUp, IconLock } from 'twenty-ui/display';
|
||||
import { Card, Section } from 'twenty-ui/layout';
|
||||
|
||||
@@ -21,17 +20,20 @@ const StyledContent = styled.div`
|
||||
padding-top: ${themeCssVariables.spacing[4]};
|
||||
`;
|
||||
|
||||
const StyledCard = styled(Card)`
|
||||
const StyledCardContainer = styled.div`
|
||||
margin-top: ${themeCssVariables.spacing[4]};
|
||||
overflow: hidden;
|
||||
`;
|
||||
|
||||
const StyledPill = styled(Pill)`
|
||||
border-radius: 40px;
|
||||
border: 1px solid ${themeCssVariables.border.color.light};
|
||||
const StyledPillContainer = styled.span`
|
||||
align-items: center;
|
||||
background: ${themeCssVariables.background.secondary};
|
||||
border: 1px solid ${themeCssVariables.border.color.light};
|
||||
border-radius: 40px;
|
||||
color: ${themeCssVariables.font.color.tertiary};
|
||||
display: inline-flex;
|
||||
font-weight: ${themeCssVariables.font.weight.medium};
|
||||
gap: ${themeCssVariables.spacing[1]};
|
||||
padding: ${themeCssVariables.spacing[1]} ${themeCssVariables.spacing[2]};
|
||||
`;
|
||||
|
||||
@@ -56,27 +58,34 @@ export const SettingsRolePermissionsObjectLevelRecordLevelSection = ({
|
||||
<H2Title
|
||||
title={t`Record-level`}
|
||||
description={t`Ability to filter the records a user can interact with`}
|
||||
adornment={<StyledPill label={t`Organization`} Icon={IconLock} />}
|
||||
adornment={
|
||||
<StyledPillContainer>
|
||||
<IconLock size={12} />
|
||||
{t`Organization`}
|
||||
</StyledPillContainer>
|
||||
}
|
||||
/>
|
||||
<StyledCard rounded>
|
||||
<SettingsOptionCardContentButton
|
||||
Icon={IconLock}
|
||||
title={t`Upgrade to access`}
|
||||
description={t`This feature is part of the Organization Plan`}
|
||||
Button={
|
||||
isBillingEnabled && (
|
||||
<Button
|
||||
title={t`Upgrade`}
|
||||
variant="primary"
|
||||
accent="blue"
|
||||
size="small"
|
||||
Icon={IconArrowUp}
|
||||
onClick={() => navigateSettings(SettingsPath.Billing)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
/>
|
||||
</StyledCard>
|
||||
<StyledCardContainer>
|
||||
<Card rounded>
|
||||
<SettingsOptionCardContentButton
|
||||
Icon={IconLock}
|
||||
title={t`Upgrade to access`}
|
||||
description={t`This feature is part of the Organization Plan`}
|
||||
Button={
|
||||
isBillingEnabled && (
|
||||
<Button
|
||||
title={t`Upgrade`}
|
||||
variant="primary"
|
||||
accent="blue"
|
||||
size="small"
|
||||
Icon={IconArrowUp}
|
||||
onClick={() => navigateSettings(SettingsPath.Billing)}
|
||||
/>
|
||||
)
|
||||
}
|
||||
/>
|
||||
</Card>
|
||||
</StyledCardContainer>
|
||||
</Section>
|
||||
);
|
||||
}
|
||||
|
||||
+7
-15
@@ -4,22 +4,10 @@ import { TableHeader } from '@/ui/layout/table/components/TableHeader';
|
||||
import { useAtomFamilyStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomFamilyStateValue';
|
||||
import { useSetAtomFamilyState } from '@/ui/utilities/state/jotai/hooks/useSetAtomFamilyState';
|
||||
import { TableRow } from '@/ui/layout/table/components/TableRow';
|
||||
import { styled } from '@linaria/react';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { Checkbox } from 'twenty-ui/input';
|
||||
import { themeCssVariables } from 'twenty-ui/theme-constants';
|
||||
|
||||
const StyledNameHeader = styled(TableHeader)`
|
||||
flex: 1;
|
||||
`;
|
||||
|
||||
const StyledActionsHeader = styled(TableHeader)`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
padding-right: ${themeCssVariables.spacing[1]};
|
||||
`;
|
||||
|
||||
type SettingsRolePermissionsObjectsTableHeaderProps = {
|
||||
roleId: string;
|
||||
objectPermissionsConfig: SettingsRolePermissionsObjectPermission[];
|
||||
@@ -50,8 +38,12 @@ export const SettingsRolePermissionsObjectsTableHeader = ({
|
||||
|
||||
return (
|
||||
<TableRow>
|
||||
<StyledNameHeader>{t`All Objects`}</StyledNameHeader>
|
||||
<StyledActionsHeader aria-label={t`Actions`}>
|
||||
<TableHeader>{t`All Objects`}</TableHeader>
|
||||
<TableHeader
|
||||
align="right"
|
||||
padding={`0 ${themeCssVariables.spacing[1]} 0 ${themeCssVariables.spacing[2]}`}
|
||||
aria-label={t`Actions`}
|
||||
>
|
||||
<Checkbox
|
||||
checked={allPermissionsEnabled}
|
||||
indeterminate={somePermissionsEnabled && !allPermissionsEnabled}
|
||||
@@ -69,7 +61,7 @@ export const SettingsRolePermissionsObjectsTableHeader = ({
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</StyledActionsHeader>
|
||||
</TableHeader>
|
||||
</TableRow>
|
||||
);
|
||||
};
|
||||
|
||||
+13
-26
@@ -8,14 +8,6 @@ import { plural } from '@lingui/core/macro';
|
||||
import { Checkbox, CheckboxAccent } from 'twenty-ui/input';
|
||||
import { themeCssVariables } from 'twenty-ui/theme-constants';
|
||||
|
||||
const StyledPermissionCell = styled(TableCell)`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex: 1;
|
||||
gap: ${themeCssVariables.spacing[1]};
|
||||
padding-left: ${themeCssVariables.spacing[2]};
|
||||
`;
|
||||
|
||||
const StyledPermissionContent = styled.div`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
@@ -32,18 +24,6 @@ const StyledOverrideInfo = styled.div`
|
||||
display: flex;
|
||||
gap: ${themeCssVariables.spacing[1]};
|
||||
`;
|
||||
const StyledCheckboxCell = styled(TableCell)`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
padding-right: ${themeCssVariables.spacing[1]};
|
||||
`;
|
||||
|
||||
const StyledTableRow = styled(TableRow)<{ isDisabled: boolean }>`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
cursor: ${({ isDisabled }) => (isDisabled ? 'default' : 'pointer')};
|
||||
`;
|
||||
|
||||
type SettingsRolePermissionsObjectsTableRowProps = {
|
||||
permission: SettingsRolePermissionsObjectPermission;
|
||||
@@ -68,8 +48,11 @@ export const SettingsRolePermissionsObjectsTableRow = ({
|
||||
};
|
||||
|
||||
return (
|
||||
<StyledTableRow onClick={handleRowClick} isDisabled={isDisabled}>
|
||||
<StyledPermissionCell>
|
||||
<TableRow
|
||||
onClick={handleRowClick}
|
||||
cursor={isDisabled ? 'default' : 'pointer'}
|
||||
>
|
||||
<TableCell gap={themeCssVariables.spacing[1]}>
|
||||
<StyledPermissionContent>
|
||||
<PermissionIcon
|
||||
permission={permission.key as SettingsRoleObjectPermissionKey}
|
||||
@@ -96,15 +79,19 @@ export const SettingsRolePermissionsObjectsTableRow = ({
|
||||
</>
|
||||
) : null}
|
||||
</StyledOverrideInfo>
|
||||
</StyledPermissionCell>
|
||||
<StyledCheckboxCell onClick={(e) => e.stopPropagation()}>
|
||||
</TableCell>
|
||||
<TableCell
|
||||
align="right"
|
||||
padding={`0 ${themeCssVariables.spacing[1]} 0 ${themeCssVariables.spacing[2]}`}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<Checkbox
|
||||
checked={permission.value ?? false}
|
||||
onChange={() => permission.setValue(!permission.value)}
|
||||
disabled={isDisabled}
|
||||
accent={isRevoked ? CheckboxAccent.Orange : CheckboxAccent.Blue}
|
||||
/>
|
||||
</StyledCheckboxCell>
|
||||
</StyledTableRow>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
};
|
||||
|
||||
+18
-16
@@ -20,7 +20,7 @@ const StyledTableRows = styled.div`
|
||||
padding-top: ${themeCssVariables.spacing[2]};
|
||||
`;
|
||||
|
||||
const StyledCard = styled(Card)`
|
||||
const StyledCardContainer = styled.div`
|
||||
margin-bottom: ${themeCssVariables.spacing[4]};
|
||||
`;
|
||||
|
||||
@@ -58,21 +58,23 @@ export const SettingsRolePermissionsSettingsSection = ({
|
||||
<Section>
|
||||
<H2Title title={t`Settings`} description={t`Settings permissions`} />
|
||||
{shouldShowAllAccessToggle && (
|
||||
<StyledCard rounded>
|
||||
<SettingsOptionCardContentToggle
|
||||
Icon={IconSettings}
|
||||
title={t`Settings All Access`}
|
||||
description={t`Ability to edit all settings`}
|
||||
checked={settingsDraftRole.canUpdateAllSettings}
|
||||
disabled={!isEditable}
|
||||
onChange={() => {
|
||||
setSettingsDraftRole({
|
||||
...settingsDraftRole,
|
||||
canUpdateAllSettings: !settingsDraftRole.canUpdateAllSettings,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</StyledCard>
|
||||
<StyledCardContainer>
|
||||
<Card rounded>
|
||||
<SettingsOptionCardContentToggle
|
||||
Icon={IconSettings}
|
||||
title={t`Settings All Access`}
|
||||
description={t`Ability to edit all settings`}
|
||||
checked={settingsDraftRole.canUpdateAllSettings}
|
||||
disabled={!isEditable}
|
||||
onChange={() => {
|
||||
setSettingsDraftRole({
|
||||
...settingsDraftRole,
|
||||
canUpdateAllSettings: !settingsDraftRole.canUpdateAllSettings,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</Card>
|
||||
</StyledCardContainer>
|
||||
)}
|
||||
<AnimatedExpandableContainer
|
||||
isExpanded={
|
||||
|
||||
+6
-10
@@ -1,6 +1,5 @@
|
||||
import { TableHeader } from '@/ui/layout/table/components/TableHeader';
|
||||
import { TableRow } from '@/ui/layout/table/components/TableRow';
|
||||
import { styled } from '@linaria/react';
|
||||
import { t } from '@lingui/core/macro';
|
||||
import { Checkbox } from 'twenty-ui/input';
|
||||
|
||||
@@ -17,13 +16,6 @@ type SettingsRolePermissionsSettingsTableHeaderProps = {
|
||||
isEditable: boolean;
|
||||
};
|
||||
|
||||
const StyledActionsHeader = styled(TableHeader)`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
padding-right: ${themeCssVariables.spacing[1]};
|
||||
`;
|
||||
|
||||
export const SettingsRolePermissionsSettingsTableHeader = ({
|
||||
roleId,
|
||||
settingsPermissionsConfig,
|
||||
@@ -55,7 +47,11 @@ export const SettingsRolePermissionsSettingsTableHeader = ({
|
||||
<TableRow gridAutoColumns="3fr 4fr 24px">
|
||||
<TableHeader>{t`Name`}</TableHeader>
|
||||
<TableHeader>{t`Description`}</TableHeader>
|
||||
<StyledActionsHeader aria-label={t`Actions`}>
|
||||
<TableHeader
|
||||
align="right"
|
||||
padding={`0 ${themeCssVariables.spacing[1]} 0 ${themeCssVariables.spacing[2]}`}
|
||||
aria-label={t`Actions`}
|
||||
>
|
||||
<Checkbox
|
||||
checked={allSettingsPermissionsEnabled}
|
||||
indeterminate={
|
||||
@@ -78,7 +74,7 @@ export const SettingsRolePermissionsSettingsTableHeader = ({
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</StyledActionsHeader>
|
||||
</TableHeader>
|
||||
</TableRow>
|
||||
);
|
||||
};
|
||||
|
||||
+14
-28
@@ -10,32 +10,14 @@ import { useContext } from 'react';
|
||||
import { ThemeContext, themeCssVariables } from 'twenty-ui/theme-constants';
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
const StyledTableRow = styled(TableRow)<{ isDisabled: boolean }>`
|
||||
cursor: ${({ isDisabled }) => (isDisabled ? 'default' : 'pointer')};
|
||||
`;
|
||||
|
||||
const StyledName = styled.span`
|
||||
color: ${themeCssVariables.font.color.primary};
|
||||
`;
|
||||
|
||||
const StyledDescription = styled(StyledName)`
|
||||
const StyledDescription = styled.span`
|
||||
color: ${themeCssVariables.font.color.secondary};
|
||||
`;
|
||||
|
||||
const StyledPermissionCell = styled(TableCell)`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex: 1;
|
||||
gap: ${themeCssVariables.spacing[2]};
|
||||
`;
|
||||
|
||||
const StyledCheckboxCell = styled(TableCell)`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
padding-right: ${themeCssVariables.spacing[1]};
|
||||
`;
|
||||
|
||||
const StyledIconContainer = styled.div`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
@@ -112,13 +94,13 @@ export const SettingsRolePermissionsSettingsTableRow = ({
|
||||
};
|
||||
|
||||
return (
|
||||
<StyledTableRow
|
||||
<TableRow
|
||||
key={permission.key}
|
||||
gridAutoColumns="3fr 4fr 24px"
|
||||
onClick={handleRowClick}
|
||||
isDisabled={isDisabled}
|
||||
cursor={isDisabled ? 'default' : 'pointer'}
|
||||
>
|
||||
<StyledPermissionCell>
|
||||
<TableCell gap={themeCssVariables.spacing[2]}>
|
||||
<StyledIconContainer>
|
||||
<permission.Icon
|
||||
size={theme.icon.size.md}
|
||||
@@ -127,17 +109,21 @@ export const SettingsRolePermissionsSettingsTableRow = ({
|
||||
/>
|
||||
</StyledIconContainer>
|
||||
<StyledName>{permission.name}</StyledName>
|
||||
</StyledPermissionCell>
|
||||
<StyledPermissionCell>
|
||||
</TableCell>
|
||||
<TableCell gap={themeCssVariables.spacing[2]}>
|
||||
<StyledDescription>{permission.description}</StyledDescription>
|
||||
</StyledPermissionCell>
|
||||
<StyledCheckboxCell onClick={(e) => e.stopPropagation()}>
|
||||
</TableCell>
|
||||
<TableCell
|
||||
align="right"
|
||||
padding={`0 ${themeCssVariables.spacing[1]} 0 ${themeCssVariables.spacing[2]}`}
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
>
|
||||
<Checkbox
|
||||
checked={isChecked}
|
||||
disabled={isDisabled}
|
||||
onChange={(event) => handleChange(event.target.checked)}
|
||||
/>
|
||||
</StyledCheckboxCell>
|
||||
</StyledTableRow>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
};
|
||||
|
||||
+18
-16
@@ -21,7 +21,7 @@ const StyledTableRows = styled.div`
|
||||
padding-top: ${themeCssVariables.spacing[2]};
|
||||
`;
|
||||
|
||||
const StyledCard = styled(Card)`
|
||||
const StyledCardContainer = styled.div`
|
||||
margin-bottom: ${themeCssVariables.spacing[4]};
|
||||
`;
|
||||
|
||||
@@ -59,21 +59,23 @@ export const SettingsRolePermissionsToolSection = ({
|
||||
<Section>
|
||||
<H2Title title={t`Actions`} description={t`Actions permissions`} />
|
||||
{shouldShowAllAccessToggle && (
|
||||
<StyledCard rounded>
|
||||
<SettingsOptionCardContentToggle
|
||||
Icon={IconTool}
|
||||
title={t`All Actions Access`}
|
||||
description={t`Grants permission to perform all available actions without restriction`}
|
||||
checked={settingsDraftRole.canAccessAllTools}
|
||||
disabled={!isEditable}
|
||||
onChange={() => {
|
||||
setSettingsDraftRole({
|
||||
...settingsDraftRole,
|
||||
canAccessAllTools: !settingsDraftRole.canAccessAllTools,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</StyledCard>
|
||||
<StyledCardContainer>
|
||||
<Card rounded>
|
||||
<SettingsOptionCardContentToggle
|
||||
Icon={IconTool}
|
||||
title={t`All Actions Access`}
|
||||
description={t`Grants permission to perform all available actions without restriction`}
|
||||
checked={settingsDraftRole.canAccessAllTools}
|
||||
disabled={!isEditable}
|
||||
onChange={() => {
|
||||
setSettingsDraftRole({
|
||||
...settingsDraftRole,
|
||||
canAccessAllTools: !settingsDraftRole.canAccessAllTools,
|
||||
});
|
||||
}}
|
||||
/>
|
||||
</Card>
|
||||
</StyledCardContainer>
|
||||
)}
|
||||
<AnimatedExpandableContainer
|
||||
isExpanded={
|
||||
|
||||
+14
-10
@@ -18,9 +18,12 @@ import { getSettingsPath } from 'twenty-shared/utils';
|
||||
import { IconKey } from 'twenty-ui/display';
|
||||
import { useGetSsoIdentityProvidersQuery } from '~/generated-metadata/graphql';
|
||||
|
||||
const StyledLink = styled(Link)<{ isDisabled: boolean }>`
|
||||
const StyledLinkContainer = styled.div<{ isDisabled: boolean }>`
|
||||
pointer-events: ${({ isDisabled }) => (isDisabled ? 'none' : 'auto')};
|
||||
text-decoration: none;
|
||||
|
||||
> a {
|
||||
text-decoration: none;
|
||||
}
|
||||
`;
|
||||
|
||||
export const SettingsSSOIdentitiesProvidersListCard = () => {
|
||||
@@ -48,16 +51,17 @@ export const SettingsSSOIdentitiesProvidersListCard = () => {
|
||||
});
|
||||
|
||||
return loading || !SSOIdentitiesProviders.length ? (
|
||||
<StyledLink
|
||||
to={getSettingsPath(SettingsPath.NewSSOIdentityProvider)}
|
||||
<StyledLinkContainer
|
||||
isDisabled={currentWorkspace?.hasValidEnterpriseKey !== true}
|
||||
>
|
||||
<SettingsCard
|
||||
title={t`Add SSO Identity Provider`}
|
||||
disabled={currentWorkspace?.hasValidEnterpriseKey !== true}
|
||||
Icon={<IconKey />}
|
||||
/>
|
||||
</StyledLink>
|
||||
<Link to={getSettingsPath(SettingsPath.NewSSOIdentityProvider)}>
|
||||
<SettingsCard
|
||||
title={t`Add SSO Identity Provider`}
|
||||
disabled={currentWorkspace?.hasValidEnterpriseKey !== true}
|
||||
Icon={<IconKey />}
|
||||
/>
|
||||
</Link>
|
||||
</StyledLinkContainer>
|
||||
) : (
|
||||
<SettingsSSOIdentitiesProvidersListCardWrapper />
|
||||
);
|
||||
|
||||
+12
-8
@@ -19,8 +19,10 @@ import { useGetApprovedAccessDomainsQuery } from '~/generated-metadata/graphql';
|
||||
import { dateLocaleState } from '~/localization/states/dateLocaleState';
|
||||
import { beautifyPastDateRelativeToNow } from '~/utils/date-utils';
|
||||
|
||||
const StyledLink = styled(Link)`
|
||||
text-decoration: none;
|
||||
const StyledLinkContainer = styled.div`
|
||||
> a {
|
||||
text-decoration: none;
|
||||
}
|
||||
`;
|
||||
|
||||
export const SettingsApprovedAccessDomainsListCard = () => {
|
||||
@@ -54,12 +56,14 @@ export const SettingsApprovedAccessDomainsListCard = () => {
|
||||
};
|
||||
|
||||
return loading || !approvedAccessDomains.length ? (
|
||||
<StyledLink to={getSettingsPath(SettingsPath.NewApprovedAccessDomain)}>
|
||||
<SettingsCard
|
||||
title={t`Add Approved Access Domain`}
|
||||
Icon={<IconMailCog />}
|
||||
/>
|
||||
</StyledLink>
|
||||
<StyledLinkContainer>
|
||||
<Link to={getSettingsPath(SettingsPath.NewApprovedAccessDomain)}>
|
||||
<SettingsCard
|
||||
title={t`Add Approved Access Domain`}
|
||||
Icon={<IconMailCog />}
|
||||
/>
|
||||
</Link>
|
||||
</StyledLinkContainer>
|
||||
) : (
|
||||
<>
|
||||
<SettingsSecurityApprovedAccessDomainValidationEffect />
|
||||
|
||||
Reference in New Issue
Block a user