diff --git a/packages/twenty-front/src/modules/app/components/SettingsRoutes.tsx b/packages/twenty-front/src/modules/app/components/SettingsRoutes.tsx index fe62db2515..9fa0d10da8 100644 --- a/packages/twenty-front/src/modules/app/components/SettingsRoutes.tsx +++ b/packages/twenty-front/src/modules/app/components/SettingsRoutes.tsx @@ -182,22 +182,24 @@ const SettingsLegalDpaNew = lazy(() => })), ); -const SettingsWorkspaceEmail = lazy(() => - import('~/pages/settings/email/SettingsWorkspaceEmail').then((module) => ({ - default: module.SettingsWorkspaceEmail, - })), +const SettingsWorkspaceCommunications = lazy(() => + import('~/pages/settings/communications/SettingsWorkspaceCommunications').then( + (module) => ({ + default: module.SettingsWorkspaceCommunications, + }), + ), ); -const SettingsWorkspaceEmailGroupChannelDetail = lazy(() => - import('~/pages/settings/email/SettingsWorkspaceEmailGroupChannelDetail').then( +const SettingsWorkspaceCommunicationGroupChannelDetail = lazy(() => + import('~/pages/settings/communications/SettingsWorkspaceCommunicationGroupChannelDetail').then( (module) => ({ - default: module.SettingsWorkspaceEmailGroupChannelDetail, + default: module.SettingsWorkspaceCommunicationGroupChannelDetail, }), ), ); const SettingsWorkspaceNewUnsubscribeTopic = lazy(() => - import('~/pages/settings/email/SettingsWorkspaceNewUnsubscribeTopic').then( + import('~/pages/settings/communications/SettingsWorkspaceNewUnsubscribeTopic').then( (module) => ({ default: module.SettingsWorkspaceNewUnsubscribeTopic, }), @@ -205,7 +207,7 @@ const SettingsWorkspaceNewUnsubscribeTopic = lazy(() => ); const SettingsWorkspaceUnsubscribeTopicDetail = lazy(() => - import('~/pages/settings/email/SettingsWorkspaceUnsubscribeTopicDetail').then( + import('~/pages/settings/communications/SettingsWorkspaceUnsubscribeTopicDetail').then( (module) => ({ default: module.SettingsWorkspaceUnsubscribeTopicDetail, }), @@ -703,8 +705,8 @@ export const SettingsRoutes = ({ isAdminPageEnabled }: SettingsRoutesProps) => ( > } /> } + path={SettingsPath.WorkspaceCommunications} + element={} /> ( /> } + element={} /> { href: getSettingsPath(SettingsPath.General), }, { - children: t`Email`, - href: getSettingsPath(SettingsPath.WorkspaceEmail), + children: t`Communications`, + href: getSettingsPath(SettingsPath.WorkspaceCommunications), }, { children: t`New Email Channel` }, ]} @@ -63,7 +63,7 @@ export const SettingsAccountsNewEmailGroupChannel = () => { isSaveDisabled={!canSave} isCancelDisabled={loading} isLoading={loading} - onCancel={() => navigate(SettingsPath.WorkspaceEmail)} + onCancel={() => navigate(SettingsPath.WorkspaceCommunications)} onSave={handleSave} /> } @@ -80,6 +80,11 @@ export const SettingsAccountsNewEmailGroupChannel = () => { placeholder="support@mycompany.com" value={handle} onChange={setHandle} + onInputEnter={() => { + if (canSave) { + handleSave(); + } + }} disabled={loading} /> diff --git a/packages/twenty-front/src/modules/settings/components/SettingsDnsRecordsTable.tsx b/packages/twenty-front/src/modules/settings/components/SettingsDnsRecordsTable.tsx index 7b7c4da060..d08d921534 100644 --- a/packages/twenty-front/src/modules/settings/components/SettingsDnsRecordsTable.tsx +++ b/packages/twenty-front/src/modules/settings/components/SettingsDnsRecordsTable.tsx @@ -1,3 +1,4 @@ +import { SettingsTextInput } from '@/ui/input/components/SettingsTextInput'; import { Table } from '@/ui/layout/table/components/Table'; import { TableCell } from '@/ui/layout/table/components/TableCell'; import { TableHeader } from '@/ui/layout/table/components/TableHeader'; @@ -6,40 +7,41 @@ import { styled } from '@linaria/react'; import { t } from '@lingui/core/macro'; import { capitalize, isDefined } from 'twenty-shared/utils'; import { Status } from 'twenty-ui/data-display'; -import { OverflowingTextWithTooltip } from 'twenty-ui/surfaces'; import { type ThemeColor } from 'twenty-ui/theme'; +import { themeCssVariables } from 'twenty-ui/theme-constants'; import { useCopyToClipboard } from '~/hooks/useCopyToClipboard'; -type RecordStatus = { - status: string; - statusColor: ThemeColor; -}; - -type DnsRecordBase = { +type DnsRecord = { type: string; key: string; value: string; priority?: number | null; ttl?: string; + status?: string; + statusColor?: ThemeColor; }; -type DnsRecord = DnsRecordBase | (DnsRecordBase & RecordStatus); - type SettingsDnsRecordsTableProps = { records: DnsRecord[]; }; -const StyledTableRowContainer = styled.div` - > * > * { - max-width: 100%; - min-width: 0; - overflow: hidden; - } +const StyledRecordTableRow = styled(TableRow)` + margin-top: ${themeCssVariables.spacing[2]}; `; -const StyledTableCellFontWrapper = styled.div` - display: contents; - font-family: monospace; +const StyledCopyableCell = styled.div` + min-width: 0; + width: 100%; + + & input { + cursor: pointer; + pointer-events: none; + } + + &:hover input { + background-color: ${themeCssVariables.background.transparent.light}; + border-color: ${themeCssVariables.border.color.strong}; + } `; export const SettingsDnsRecordsTable = ({ @@ -51,91 +53,101 @@ export const SettingsDnsRecordsTable = ({ return null; } - const hasTtlRecords = records.some((record) => isDefined(record.ttl)); - const hasStatusRecords = records.some((record) => 'status' in record); - const hasPriorityRecords = records.some((record) => + const hasPriorityColumn = records.some((record) => isDefined(record.priority), ); + const hasTtlColumn = records.some((record) => isDefined(record.ttl)); + const hasStatusColumn = records.some((record) => isDefined(record.status)); - const buildGridColumns = () => { - const baseColumns = ['max-content', '1fr', '1fr']; - - if (hasPriorityRecords) baseColumns.push('max-content'); - if (hasTtlRecords) baseColumns.push('max-content'); - if (hasStatusRecords) baseColumns.push('max-content'); - - return baseColumns.join(' '); - }; - - const gridAutoColumns = buildGridColumns(); + const gridAutoColumns = [ + '100px', + 'minmax(0, 1fr)', + 'minmax(0, 1.5fr)', + ...(hasPriorityColumn ? ['max-content'] : []), + ...(hasTtlColumn ? ['max-content'] : []), + ...(hasStatusColumn ? ['max-content'] : []), + ].join(' '); return ( - - - {t`Type`} - {t`Key`} - {t`Value`} - {hasPriorityRecords && ( - {t`Priority`} + + {t`Type`} + {t`Name`} + {t`Value`} + {hasPriorityColumn && ( + {t`Priority`} + )} + {hasTtlColumn && {t`TTL`}} + {hasStatusColumn && ( + {t`Status`} + )} + + + {records.map((record, index) => ( + + + + copyToClipboard(record.type, t`Copied to clipboard`) + } + > + + + + + + copyToClipboard(record.key, t`Copied to clipboard`) + } + > + + + + + + copyToClipboard(record.value, t`Copied to clipboard`) + } + > + + + + {hasPriorityColumn && ( + {record.priority} )} - {hasTtlRecords && {t`TTL`}} - {hasStatusRecords && ( - {t`Status`} + {hasTtlColumn && {record.ttl}} + {hasStatusColumn && ( + + {isDefined(record.status) && isDefined(record.statusColor) && ( + + )} + )} - - - - {records.map((record) => ( - - - {record.type} - - { - copyToClipboard(record.key || ''); - }} - > - - - - - - { - copyToClipboard(record.value); - }} - > - - - - - {hasPriorityRecords && ( - - {record.priority} - - )} - {hasTtlRecords && ( - - {record.ttl} - - )} - {hasStatusRecords && ( - - - {'status' in record ? ( - - ) : null} - - - )} - - + ))}
); diff --git a/packages/twenty-front/src/modules/settings/components/SettingsTableListSection.tsx b/packages/twenty-front/src/modules/settings/components/SettingsTableListSection.tsx index 3996bad44e..3eee717fdd 100644 --- a/packages/twenty-front/src/modules/settings/components/SettingsTableListSection.tsx +++ b/packages/twenty-front/src/modules/settings/components/SettingsTableListSection.tsx @@ -6,7 +6,7 @@ import { Table } from '@/ui/layout/table/components/Table'; import { TableCell } from '@/ui/layout/table/components/TableCell'; import { TableHeader } from '@/ui/layout/table/components/TableHeader'; import { TableRow } from '@/ui/layout/table/components/TableRow'; -import { IconPlus } from 'twenty-ui/icon'; +import { IconChevronRight, IconPlus } from 'twenty-ui/icon'; import { H2Title } from 'twenty-ui/typography'; import { Button } from 'twenty-ui/input'; import { Section } from 'twenty-ui/layout'; @@ -53,6 +53,7 @@ type SettingsTableListSectionProps = { items: Item[]; columns: SettingsTableListSectionColumn[]; gridAutoColumns: string; + showRowChevron?: boolean; onRowClick?: (item: Item) => void; footerButtonLabel: string; onFooterButtonClick: () => void; @@ -67,55 +68,71 @@ export const SettingsTableListSection = < items, columns, gridAutoColumns, + showRowChevron = false, onRowClick, footerButtonLabel, onFooterButtonClick, -}: SettingsTableListSectionProps) => ( -
- - {items.length > 0 && ( - - - {columns.map((column) => ( - - {column.label} - - ))} - - - {items.map((item) => ( - - onRowClick(item) : undefined} - > - {columns.map((column) => ( - - - - ))} - - - ))} - -
- )} - -
+ ); +}; diff --git a/packages/twenty-front/src/modules/settings/hooks/useSettingsNavigationItems.tsx b/packages/twenty-front/src/modules/settings/hooks/useSettingsNavigationItems.tsx index 4a795f4bd3..42ee67b27a 100644 --- a/packages/twenty-front/src/modules/settings/hooks/useSettingsNavigationItems.tsx +++ b/packages/twenty-front/src/modules/settings/hooks/useSettingsNavigationItems.tsx @@ -28,6 +28,7 @@ import { IconLayout, IconMail, IconMessage, + IconMessageCircle, IconPlug, IconServer, IconSettings, @@ -173,9 +174,9 @@ const useSettingsNavigationItems = (): SettingsNavigationSection[] => { isHidden: !permissionMap[PermissionFlagType.AI_SETTINGS], }, { - label: t`Email`, - path: SettingsPath.WorkspaceEmail, - Icon: IconMail, + label: t`Communications`, + path: SettingsPath.WorkspaceCommunications, + Icon: IconMessageCircle, isHidden: !isEmailGroupFeatureEnabled || !permissionMap[PermissionFlagType.WORKSPACE], diff --git a/packages/twenty-front/src/modules/settings/unsubscribe-topics/components/SettingsWorkspaceUnsubscribeTopicSection.tsx b/packages/twenty-front/src/modules/settings/unsubscribe-topics/components/SettingsWorkspaceUnsubscribeTopicSection.tsx index d43b550751..cdffd6b53c 100644 --- a/packages/twenty-front/src/modules/settings/unsubscribe-topics/components/SettingsWorkspaceUnsubscribeTopicSection.tsx +++ b/packages/twenty-front/src/modules/settings/unsubscribe-topics/components/SettingsWorkspaceUnsubscribeTopicSection.tsx @@ -1,18 +1,14 @@ -import { useLazyQuery } from '@apollo/client/react'; import { useLingui } from '@lingui/react/macro'; import { useUnsubscribeTopics } from '@/activities/emails/hooks/useUnsubscribeTopics'; import { SettingsTableListSection } from '@/settings/components/SettingsTableListSection'; -import { GET_UNSUBSCRIBE_PAGE_PREVIEW_URL } from '@/settings/unsubscribe-topics/graphql/queries/getUnsubscribePagePreviewUrl'; import { SettingsPath } from 'twenty-shared/types'; -import { isDefined } from 'twenty-shared/utils'; import { type UnsubscribeTopicsQuery, UnsubscribeTopicVisibility, } from '~/generated-metadata/graphql'; -import { Status } from 'twenty-ui/data-display'; -import { IconExternalLink } from 'twenty-ui/icon'; -import { Button } from 'twenty-ui/input'; +import { Pill, Status } from 'twenty-ui/data-display'; +import { IconLock } from 'twenty-ui/icon'; import { useNavigateSettings } from '~/hooks/useNavigateSettings'; type UnsubscribeTopic = UnsubscribeTopicsQuery['unsubscribeTopics'][number]; @@ -21,49 +17,25 @@ export const SettingsWorkspaceUnsubscribeTopicSection = () => { const { t } = useLingui(); const navigateSettings = useNavigateSettings(); const { unsubscribeTopics } = useUnsubscribeTopics(); - const [getPreviewUrl] = useLazyQuery<{ - unsubscribePagePreviewUrl: string; - }>(GET_UNSUBSCRIBE_PAGE_PREVIEW_URL); - // Open the tab synchronously on click (so it isn't popup-blocked), then point - // it at the freshly minted preview URL once the query resolves. - const handlePreview = () => { - const previewWindow = window.open('', '_blank'); - - void getPreviewUrl() - .then(({ data }) => { - const url = data?.unsubscribePagePreviewUrl; - - if (isDefined(previewWindow) && isDefined(url)) { - previewWindow.location.href = url; - } else { - previewWindow?.close(); - } - }) - .catch(() => previewWindow?.close()); - }; + const title = t`Unsubscribe topics`; + const description = t`Email categories recipients can opt out of`; + const organizationPill = ; return ( - title={t`Unsubscribe Topics`} - description={t`Email categories recipients can opt out of.`} - headerAdornment={ -