message campaign redesign (#22508)
<!-- This is an auto-generated description by cubic. --> <a href="https://cubic.dev/pr/twentyhq/twenty/pull/22508?utm_source=github" target="_blank" rel="noopener noreferrer" data-no-image-dialog="true"><picture><source media="(prefers-color-scheme: dark)" srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source media="(prefers-color-scheme: light)" srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img alt="Review in cubic" src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a> <!-- End of auto-generated description by cubic. -->
This commit is contained in:
@@ -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) => (
|
||||
>
|
||||
<Route path={SettingsPath.General} element={<SettingsGeneral />} />
|
||||
<Route
|
||||
path={SettingsPath.WorkspaceEmail}
|
||||
element={<SettingsWorkspaceEmail />}
|
||||
path={SettingsPath.WorkspaceCommunications}
|
||||
element={<SettingsWorkspaceCommunications />}
|
||||
/>
|
||||
<Route
|
||||
path={SettingsPath.NewEmailGroupChannel}
|
||||
@@ -712,7 +714,7 @@ export const SettingsRoutes = ({ isAdminPageEnabled }: SettingsRoutesProps) => (
|
||||
/>
|
||||
<Route
|
||||
path={SettingsPath.EmailGroupChannelDetail}
|
||||
element={<SettingsWorkspaceEmailGroupChannelDetail />}
|
||||
element={<SettingsWorkspaceCommunicationGroupChannelDetail />}
|
||||
/>
|
||||
<Route
|
||||
path={SettingsPath.NewUnsubscribeTopic}
|
||||
|
||||
+8
-3
@@ -53,8 +53,8 @@ export const SettingsAccountsNewEmailGroupChannel = () => {
|
||||
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}
|
||||
/>
|
||||
</Section>
|
||||
|
||||
+107
-95
@@ -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 (
|
||||
<Table>
|
||||
<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>
|
||||
<TableRow gridAutoColumns={gridAutoColumns}>
|
||||
<TableHeader>{t`Type`}</TableHeader>
|
||||
<TableHeader>{t`Name`}</TableHeader>
|
||||
<TableHeader>{t`Value`}</TableHeader>
|
||||
{hasPriorityColumn && (
|
||||
<TableHeader align="center">{t`Priority`}</TableHeader>
|
||||
)}
|
||||
{hasTtlColumn && <TableHeader align="center">{t`TTL`}</TableHeader>}
|
||||
{hasStatusColumn && (
|
||||
<TableHeader align="center">{t`Status`}</TableHeader>
|
||||
)}
|
||||
</TableRow>
|
||||
|
||||
{records.map((record, index) => (
|
||||
<StyledRecordTableRow
|
||||
key={`${index}-${record.type}-${record.key}`}
|
||||
gridAutoColumns={gridAutoColumns}
|
||||
>
|
||||
<TableCell>
|
||||
<StyledCopyableCell
|
||||
onClick={() =>
|
||||
copyToClipboard(record.type, t`Copied to clipboard`)
|
||||
}
|
||||
>
|
||||
<SettingsTextInput
|
||||
instanceId={`dns-record-type-${index}`}
|
||||
value={record.type}
|
||||
sizeVariant="sm"
|
||||
disabled
|
||||
fullWidth
|
||||
/>
|
||||
</StyledCopyableCell>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<StyledCopyableCell
|
||||
onClick={() =>
|
||||
copyToClipboard(record.key, t`Copied to clipboard`)
|
||||
}
|
||||
>
|
||||
<SettingsTextInput
|
||||
instanceId={`dns-record-name-${index}`}
|
||||
value={record.key}
|
||||
sizeVariant="sm"
|
||||
disabled
|
||||
fullWidth
|
||||
/>
|
||||
</StyledCopyableCell>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<StyledCopyableCell
|
||||
onClick={() =>
|
||||
copyToClipboard(record.value, t`Copied to clipboard`)
|
||||
}
|
||||
>
|
||||
<SettingsTextInput
|
||||
instanceId={`dns-record-value-${index}`}
|
||||
value={record.value}
|
||||
sizeVariant="sm"
|
||||
disabled
|
||||
fullWidth
|
||||
/>
|
||||
</StyledCopyableCell>
|
||||
</TableCell>
|
||||
{hasPriorityColumn && (
|
||||
<TableCell align="center">{record.priority}</TableCell>
|
||||
)}
|
||||
{hasTtlRecords && <TableHeader align="center">{t`TTL`}</TableHeader>}
|
||||
{hasStatusRecords && (
|
||||
<TableHeader align="center">{t`Status`}</TableHeader>
|
||||
{hasTtlColumn && <TableCell align="center">{record.ttl}</TableCell>}
|
||||
{hasStatusColumn && (
|
||||
<TableCell align="center">
|
||||
{isDefined(record.status) && isDefined(record.statusColor) && (
|
||||
<Status
|
||||
color={record.statusColor}
|
||||
text={capitalize(record.status)}
|
||||
/>
|
||||
)}
|
||||
</TableCell>
|
||||
)}
|
||||
</TableRow>
|
||||
</StyledTableRowContainer>
|
||||
|
||||
{records.map((record) => (
|
||||
<StyledTableRowContainer key={record.value}>
|
||||
<TableRow gridAutoColumns={gridAutoColumns}>
|
||||
<TableCell>{record.type}</TableCell>
|
||||
<StyledTableCellFontWrapper>
|
||||
<TableCell
|
||||
overflow="hidden"
|
||||
onClick={() => {
|
||||
copyToClipboard(record.key || '');
|
||||
}}
|
||||
>
|
||||
<OverflowingTextWithTooltip text={record.key} />
|
||||
</TableCell>
|
||||
</StyledTableCellFontWrapper>
|
||||
|
||||
<StyledTableCellFontWrapper>
|
||||
<TableCell
|
||||
overflow="hidden"
|
||||
onClick={() => {
|
||||
copyToClipboard(record.value);
|
||||
}}
|
||||
>
|
||||
<OverflowingTextWithTooltip text={record.value} />
|
||||
</TableCell>
|
||||
</StyledTableCellFontWrapper>
|
||||
|
||||
{hasPriorityRecords && (
|
||||
<StyledTableCellFontWrapper>
|
||||
<TableCell overflow="hidden">{record.priority}</TableCell>
|
||||
</StyledTableCellFontWrapper>
|
||||
)}
|
||||
{hasTtlRecords && (
|
||||
<StyledTableCellFontWrapper>
|
||||
<TableCell overflow="hidden">{record.ttl}</TableCell>
|
||||
</StyledTableCellFontWrapper>
|
||||
)}
|
||||
{hasStatusRecords && (
|
||||
<StyledTableCellFontWrapper>
|
||||
<TableCell overflow="hidden">
|
||||
{'status' in record ? (
|
||||
<Status
|
||||
color={record.statusColor}
|
||||
text={capitalize(record.status)}
|
||||
/>
|
||||
) : null}
|
||||
</TableCell>
|
||||
</StyledTableCellFontWrapper>
|
||||
)}
|
||||
</TableRow>
|
||||
</StyledTableRowContainer>
|
||||
</StyledRecordTableRow>
|
||||
))}
|
||||
</Table>
|
||||
);
|
||||
|
||||
+66
-49
@@ -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<Item extends { id: string }> = {
|
||||
items: Item[];
|
||||
columns: SettingsTableListSectionColumn<Item>[];
|
||||
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<Item>) => (
|
||||
<Section>
|
||||
<H2Title
|
||||
title={title}
|
||||
description={description}
|
||||
adornment={headerAdornment}
|
||||
/>
|
||||
{items.length > 0 && (
|
||||
<Table>
|
||||
<TableRow gridAutoColumns={gridAutoColumns}>
|
||||
{columns.map((column) => (
|
||||
<TableHeader
|
||||
key={column.label}
|
||||
align={column.align}
|
||||
padding={HEADER_PADDING}
|
||||
>
|
||||
{column.label}
|
||||
</TableHeader>
|
||||
))}
|
||||
</TableRow>
|
||||
<StyledTableRows>
|
||||
{items.map((item) => (
|
||||
<StyledRowWrapper key={item.id} clickable={Boolean(onRowClick)}>
|
||||
<TableRow
|
||||
gridAutoColumns={gridAutoColumns}
|
||||
onClick={onRowClick ? () => onRowClick(item) : undefined}
|
||||
>
|
||||
{columns.map((column) => (
|
||||
<TableCell key={column.label} align={column.align}>
|
||||
<column.Cell item={item} />
|
||||
</TableCell>
|
||||
))}
|
||||
</TableRow>
|
||||
</StyledRowWrapper>
|
||||
))}
|
||||
</StyledTableRows>
|
||||
</Table>
|
||||
)}
|
||||
<StyledFooter>
|
||||
<Button
|
||||
Icon={IconPlus}
|
||||
title={footerButtonLabel}
|
||||
variant="secondary"
|
||||
size="small"
|
||||
onClick={onFooterButtonClick}
|
||||
}: SettingsTableListSectionProps<Item>) => {
|
||||
const resolvedGridAutoColumns = showRowChevron
|
||||
? `${gridAutoColumns} auto`
|
||||
: gridAutoColumns;
|
||||
|
||||
return (
|
||||
<Section>
|
||||
<H2Title
|
||||
title={title}
|
||||
description={description}
|
||||
adornment={headerAdornment}
|
||||
/>
|
||||
</StyledFooter>
|
||||
</Section>
|
||||
);
|
||||
{items.length > 0 && (
|
||||
<Table>
|
||||
<TableRow gridAutoColumns={resolvedGridAutoColumns}>
|
||||
{columns.map((column) => (
|
||||
<TableHeader
|
||||
key={column.label}
|
||||
align={column.align}
|
||||
padding={HEADER_PADDING}
|
||||
>
|
||||
{column.label}
|
||||
</TableHeader>
|
||||
))}
|
||||
{showRowChevron && <TableHeader padding={HEADER_PADDING} />}
|
||||
</TableRow>
|
||||
<StyledTableRows>
|
||||
{items.map((item) => (
|
||||
<StyledRowWrapper key={item.id} clickable={Boolean(onRowClick)}>
|
||||
<TableRow
|
||||
gridAutoColumns={resolvedGridAutoColumns}
|
||||
onClick={onRowClick ? () => onRowClick(item) : undefined}
|
||||
>
|
||||
{columns.map((column) => (
|
||||
<TableCell key={column.label} align={column.align}>
|
||||
<column.Cell item={item} />
|
||||
</TableCell>
|
||||
))}
|
||||
{showRowChevron && (
|
||||
<TableCell
|
||||
align="right"
|
||||
color={themeCssVariables.font.color.light}
|
||||
>
|
||||
<IconChevronRight size={16} />
|
||||
</TableCell>
|
||||
)}
|
||||
</TableRow>
|
||||
</StyledRowWrapper>
|
||||
))}
|
||||
</StyledTableRows>
|
||||
</Table>
|
||||
)}
|
||||
<StyledFooter>
|
||||
<Button
|
||||
Icon={IconPlus}
|
||||
title={footerButtonLabel}
|
||||
variant="secondary"
|
||||
size="small"
|
||||
onClick={onFooterButtonClick}
|
||||
/>
|
||||
</StyledFooter>
|
||||
</Section>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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],
|
||||
|
||||
+12
-39
@@ -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 = <Pill Icon={IconLock} label={t`Organization`} />;
|
||||
|
||||
return (
|
||||
<SettingsTableListSection<UnsubscribeTopic>
|
||||
title={t`Unsubscribe Topics`}
|
||||
description={t`Email categories recipients can opt out of.`}
|
||||
headerAdornment={
|
||||
<Button
|
||||
title={t`Preview`}
|
||||
variant="secondary"
|
||||
size="small"
|
||||
Icon={IconExternalLink}
|
||||
onClick={handlePreview}
|
||||
/>
|
||||
}
|
||||
title={title}
|
||||
description={description}
|
||||
headerAdornment={organizationPill}
|
||||
items={unsubscribeTopics}
|
||||
columns={[
|
||||
{
|
||||
label: t`Name`,
|
||||
label: t`Topic`,
|
||||
Cell: ({ item }) => <>{item.name ?? t`Untitled topic`}</>,
|
||||
},
|
||||
{
|
||||
label: t`Visibility`,
|
||||
align: 'right',
|
||||
Cell: ({ item }) =>
|
||||
item.visibility === UnsubscribeTopicVisibility.PUBLIC ? (
|
||||
<Status color="blue" text={t`Public`} />
|
||||
@@ -73,12 +45,13 @@ export const SettingsWorkspaceUnsubscribeTopicSection = () => {
|
||||
},
|
||||
]}
|
||||
gridAutoColumns="1fr 1fr"
|
||||
showRowChevron
|
||||
onRowClick={(topic) =>
|
||||
navigateSettings(SettingsPath.UnsubscribeTopicDetail, {
|
||||
unsubscribeTopicId: topic.id,
|
||||
})
|
||||
}
|
||||
footerButtonLabel={t`Add unsubscribe topic`}
|
||||
footerButtonLabel={t`Add topic`}
|
||||
onFooterButtonClick={() =>
|
||||
navigateSettings(SettingsPath.NewUnsubscribeTopic)
|
||||
}
|
||||
|
||||
-23
@@ -1,23 +0,0 @@
|
||||
import { styled } from '@linaria/react';
|
||||
|
||||
import { type MessageChannel } from '@/accounts/types/MessageChannel';
|
||||
import { themeCssVariables } from 'twenty-ui/theme-constants';
|
||||
|
||||
const StyledForwardingCell = styled.div`
|
||||
color: ${themeCssVariables.font.color.tertiary};
|
||||
font-family: monospace;
|
||||
font-size: ${themeCssVariables.font.size.sm};
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
`;
|
||||
|
||||
type SettingsWorkspaceEmailGroupForwardingCellProps = {
|
||||
item: MessageChannel;
|
||||
};
|
||||
|
||||
export const SettingsWorkspaceEmailGroupForwardingCell = ({
|
||||
item,
|
||||
}: SettingsWorkspaceEmailGroupForwardingCellProps) => (
|
||||
<StyledForwardingCell>{item.handle}</StyledForwardingCell>
|
||||
);
|
||||
+8
-9
@@ -4,9 +4,10 @@ import { type MessageChannel } from '@/accounts/types/MessageChannel';
|
||||
import { useMyMessageChannels } from '@/settings/accounts/hooks/useMyMessageChannels';
|
||||
import { SettingsTableListSection } from '@/settings/components/SettingsTableListSection';
|
||||
import { SettingsWorkspaceEmailChannelDomainStatusCell } from '@/settings/workspace/components/SettingsWorkspaceEmailChannelDomainStatusCell';
|
||||
import { SettingsWorkspaceEmailGroupForwardingCell } from '@/settings/workspace/components/SettingsWorkspaceEmailGroupForwardingCell';
|
||||
import { SettingsWorkspaceEmailGroupSourceCell } from '@/settings/workspace/components/SettingsWorkspaceEmailGroupSourceCell';
|
||||
import { MessageChannelType, SettingsPath } from 'twenty-shared/types';
|
||||
import { Pill } from 'twenty-ui/data-display';
|
||||
import { IconLock } from 'twenty-ui/icon';
|
||||
import { useNavigateSettings } from '~/hooks/useNavigateSettings';
|
||||
|
||||
export const SettingsWorkspaceEmailGroupSection = () => {
|
||||
@@ -20,22 +21,20 @@ export const SettingsWorkspaceEmailGroupSection = () => {
|
||||
|
||||
return (
|
||||
<SettingsTableListSection<MessageChannel>
|
||||
title={t`Email Channels`}
|
||||
description={t`Shared addresses your workspace uses to send and receive email.`}
|
||||
title={t`Channels`}
|
||||
description={t`Addresses your workspace uses to send and receive email from shared inboxes`}
|
||||
headerAdornment={<Pill Icon={IconLock} label={t`Organization`} />}
|
||||
items={emailGroupChannels}
|
||||
columns={[
|
||||
{ label: t`Source`, Cell: SettingsWorkspaceEmailGroupSourceCell },
|
||||
{
|
||||
label: t`Forwarding address`,
|
||||
Cell: SettingsWorkspaceEmailGroupForwardingCell,
|
||||
},
|
||||
{ label: t`Email`, Cell: SettingsWorkspaceEmailGroupSourceCell },
|
||||
{
|
||||
label: t`Domain`,
|
||||
align: 'right',
|
||||
Cell: SettingsWorkspaceEmailChannelDomainStatusCell,
|
||||
},
|
||||
]}
|
||||
gridAutoColumns="1fr 1fr 1fr"
|
||||
gridAutoColumns="1fr 1fr"
|
||||
showRowChevron
|
||||
onRowClick={(channel) =>
|
||||
navigateSettings(SettingsPath.EmailGroupChannelDetail, {
|
||||
messageChannelId: channel.id,
|
||||
|
||||
-3
@@ -1,7 +1,6 @@
|
||||
import { styled } from '@linaria/react';
|
||||
|
||||
import { type MessageChannel } from '@/accounts/types/MessageChannel';
|
||||
import { IconMail } from 'twenty-ui/icon';
|
||||
import { OverflowingTextWithTooltip } from 'twenty-ui/surfaces';
|
||||
import { themeCssVariables } from 'twenty-ui/theme-constants';
|
||||
|
||||
@@ -10,7 +9,6 @@ const StyledNameCell = styled.div`
|
||||
color: ${themeCssVariables.font.color.primary};
|
||||
display: flex;
|
||||
font-weight: ${themeCssVariables.font.weight.medium};
|
||||
gap: ${themeCssVariables.spacing[2]};
|
||||
min-width: 0;
|
||||
`;
|
||||
|
||||
@@ -25,7 +23,6 @@ export const SettingsWorkspaceEmailGroupSourceCell = ({
|
||||
|
||||
return (
|
||||
<StyledNameCell>
|
||||
<IconMail size={16} />
|
||||
<OverflowingTextWithTooltip text={sourceHandle ?? '—'} />
|
||||
</StyledNameCell>
|
||||
);
|
||||
|
||||
+65
-31
@@ -3,11 +3,12 @@ import { styled } from '@linaria/react';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
import { useParams } from 'react-router-dom';
|
||||
|
||||
import { SettingsAccountsMessageChannelDetails } from '@/settings/accounts/components/SettingsAccountsMessageChannelDetails';
|
||||
import { useDeleteEmailGroupChannel } from '@/settings/accounts/hooks/useDeleteEmailGroupChannel';
|
||||
import { useMyMessageChannels } from '@/settings/accounts/hooks/useMyMessageChannels';
|
||||
|
||||
import { getEmailChannelDomain } from '@/settings/accounts/utils/getEmailChannelDomain';
|
||||
import { SettingsDnsRecordsTable } from '@/settings/components/SettingsDnsRecordsTable';
|
||||
|
||||
import { SettingsEmailingDomainVerifyButton } from '@/settings/emailing-domains/components/SettingsEmailingDomainVerifyButton';
|
||||
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
|
||||
import { SettingsSkeletonLoader } from '@/settings/components/SettingsSkeletonLoader';
|
||||
@@ -16,6 +17,7 @@ import { SettingsTextInput } from '@/ui/input/components/SettingsTextInput';
|
||||
import { SettingsPageLayout } from '@/settings/components/layout/SettingsPageLayout';
|
||||
import { ConfirmationModal } from '@/ui/layout/modal/components/ConfirmationModal';
|
||||
import { useModal } from '@/ui/layout/modal/hooks/useModal';
|
||||
import { getDocumentationUrl } from '@/support/utils/getDocumentationUrl';
|
||||
import { MessageChannelType, SettingsPath } from 'twenty-shared/types';
|
||||
import { getSettingsPath, isDefined } from 'twenty-shared/utils';
|
||||
import { GetEmailingDomainsDocument } from '~/generated-metadata/graphql';
|
||||
@@ -23,7 +25,9 @@ import { Status } from 'twenty-ui/data-display';
|
||||
import { IconCopy, IconTrash } from 'twenty-ui/icon';
|
||||
import { H2Title } from 'twenty-ui/typography';
|
||||
import { Button } from 'twenty-ui/input';
|
||||
import { InlineBanner } from 'twenty-ui/feedback';
|
||||
import { Section } from 'twenty-ui/layout';
|
||||
import { Card } from 'twenty-ui/surfaces';
|
||||
import { themeCssVariables } from 'twenty-ui/theme-constants';
|
||||
import { NotFound } from '~/pages/not-found/NotFound';
|
||||
import { getColorByEmailingDomainStatus } from '~/pages/settings/emailing-domains/utils/getEmailingDomainStatusColor';
|
||||
@@ -43,7 +47,22 @@ const StyledForwardingInputContainer = styled.div`
|
||||
margin-right: ${themeCssVariables.spacing[2]};
|
||||
`;
|
||||
|
||||
export const SettingsWorkspaceEmailGroupChannelDetail = () => {
|
||||
const StyledDomainStatusRow = styled.div`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
gap: ${themeCssVariables.spacing[2]};
|
||||
justify-content: space-between;
|
||||
padding: ${themeCssVariables.spacing[2]} ${themeCssVariables.spacing[3]};
|
||||
`;
|
||||
|
||||
const StyledDomainName = styled.div`
|
||||
color: ${themeCssVariables.font.color.secondary};
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
`;
|
||||
|
||||
export const SettingsWorkspaceCommunicationGroupChannelDetail = () => {
|
||||
const { t } = useLingui();
|
||||
const navigateSettings = useNavigateSettings();
|
||||
const { messageChannelId } = useParams<{ messageChannelId: string }>();
|
||||
@@ -80,7 +99,7 @@ export const SettingsWorkspaceEmailGroupChannelDetail = () => {
|
||||
const handleDelete = async () => {
|
||||
try {
|
||||
await deleteEmailGroupChannel(channel.id);
|
||||
navigateSettings(SettingsPath.WorkspaceEmail);
|
||||
navigateSettings(SettingsPath.WorkspaceCommunications);
|
||||
} catch {
|
||||
enqueueErrorSnackBar({
|
||||
message: t`Failed to delete email channel.`,
|
||||
@@ -97,10 +116,9 @@ export const SettingsWorkspaceEmailGroupChannelDetail = () => {
|
||||
href: getSettingsPath(SettingsPath.General),
|
||||
},
|
||||
{
|
||||
children: t`Email`,
|
||||
href: getSettingsPath(SettingsPath.WorkspaceEmail),
|
||||
children: t`Communications`,
|
||||
href: getSettingsPath(SettingsPath.WorkspaceCommunications),
|
||||
},
|
||||
{ children: sourceHandle },
|
||||
]}
|
||||
actionButton={
|
||||
<Button
|
||||
@@ -115,32 +133,22 @@ export const SettingsWorkspaceEmailGroupChannelDetail = () => {
|
||||
}
|
||||
>
|
||||
<SettingsPageContainer>
|
||||
{isDefined(emailingDomain) && (
|
||||
<Section>
|
||||
<H2Title
|
||||
title={t`Sending domain`}
|
||||
description={t`Outbound mail from this channel is sent through this domain. It must be verified before email can be delivered.`}
|
||||
adornment={
|
||||
<SettingsEmailingDomainVerifyButton
|
||||
emailingDomainId={emailingDomain.id}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
<Status
|
||||
color={getColorByEmailingDomainStatus(emailingDomain.status)}
|
||||
text={getTextByEmailingDomainStatus(emailingDomain.status)}
|
||||
/>
|
||||
{isDefined(emailingDomain.verificationRecords) && (
|
||||
<SettingsDnsRecordsTable
|
||||
records={emailingDomain.verificationRecords}
|
||||
/>
|
||||
)}
|
||||
</Section>
|
||||
)}
|
||||
<InlineBanner
|
||||
message={t`Need help to configure your shared mailbox?`}
|
||||
button={{
|
||||
title: t`Go to documentation`,
|
||||
onClick: () =>
|
||||
window.open(
|
||||
getDocumentationUrl({}),
|
||||
'_blank',
|
||||
'noopener,noreferrer',
|
||||
),
|
||||
}}
|
||||
/>
|
||||
<Section>
|
||||
<H2Title
|
||||
title={t`Source address`}
|
||||
description={t`The address your workspace sends and receives email from.`}
|
||||
title={t`Shared email`}
|
||||
description={t`The shared email you want to use.`}
|
||||
/>
|
||||
<SettingsTextInput
|
||||
instanceId="email-group-source"
|
||||
@@ -175,7 +183,33 @@ export const SettingsWorkspaceEmailGroupChannelDetail = () => {
|
||||
/>
|
||||
</StyledForwardingRow>
|
||||
</Section>
|
||||
<SettingsAccountsMessageChannelDetails messageChannel={channel} />
|
||||
{isDefined(emailingDomain) && (
|
||||
<Section>
|
||||
<H2Title
|
||||
title={t`Sending domain`}
|
||||
description={t`Outbound mail from this channel is sent through this domain. It must be verified before email can be delivered.`}
|
||||
adornment={
|
||||
<SettingsEmailingDomainVerifyButton
|
||||
emailingDomainId={emailingDomain.id}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
{isDefined(emailingDomain.verificationRecords) && (
|
||||
<SettingsDnsRecordsTable
|
||||
records={emailingDomain.verificationRecords}
|
||||
/>
|
||||
)}
|
||||
<Card rounded>
|
||||
<StyledDomainStatusRow>
|
||||
<StyledDomainName>{emailingDomain.domain}</StyledDomainName>
|
||||
<Status
|
||||
color={getColorByEmailingDomainStatus(emailingDomain.status)}
|
||||
text={getTextByEmailingDomainStatus(emailingDomain.status)}
|
||||
/>
|
||||
</StyledDomainStatusRow>
|
||||
</Card>
|
||||
</Section>
|
||||
)}
|
||||
</SettingsPageContainer>
|
||||
<ConfirmationModal
|
||||
modalInstanceId={DELETE_EMAIL_GROUP_MODAL_ID}
|
||||
+127
@@ -0,0 +1,127 @@
|
||||
import { useApolloClient, useQuery } from '@apollo/client/react';
|
||||
import { styled } from '@linaria/react';
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
|
||||
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
|
||||
import { SettingsDiscoveryHeroCard } from '@/settings/components/SettingsDiscoveryHeroCard';
|
||||
import { GET_UNSUBSCRIBE_PAGE_PREVIEW_URL } from '@/settings/unsubscribe-topics/graphql/queries/getUnsubscribePagePreviewUrl';
|
||||
import { SettingsWorkspaceUnsubscribeTopicSection } from '@/settings/unsubscribe-topics/components/SettingsWorkspaceUnsubscribeTopicSection';
|
||||
import { SettingsWorkspaceEmailGroupSection } from '@/settings/workspace/components/SettingsWorkspaceEmailGroupSection';
|
||||
import { SettingsPageLayout } from '@/settings/components/layout/SettingsPageLayout';
|
||||
import { TabList } from '@/ui/layout/tab-list/components/TabList';
|
||||
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
|
||||
import { FeatureFlagKey, SettingsPath } from 'twenty-shared/types';
|
||||
import { getSettingsPath } from 'twenty-shared/utils';
|
||||
import {
|
||||
IconBrandWhatsapp,
|
||||
IconMail,
|
||||
IconMailX,
|
||||
IconPhone,
|
||||
} from 'twenty-ui/icon';
|
||||
import { H2Title } from 'twenty-ui/typography';
|
||||
import { Section } from 'twenty-ui/layout';
|
||||
import coverDark from '~/pages/settings/communications/assets/cover-dark.png';
|
||||
import coverLight from '~/pages/settings/communications/assets/cover-light.png';
|
||||
import { SettingsCard } from '@/settings/components/SettingsCard';
|
||||
import { useContext } from 'react';
|
||||
import { ThemeContext } from 'twenty-ui/theme-constants';
|
||||
|
||||
const COMMUNICATIONS_TABS_INSTANCE_ID = 'settings-communications-tabs';
|
||||
|
||||
const StyledCardLink = styled.a`
|
||||
display: block;
|
||||
min-width: 0;
|
||||
text-decoration: none;
|
||||
`;
|
||||
|
||||
export const SettingsWorkspaceCommunications = () => {
|
||||
const { theme } = useContext(ThemeContext);
|
||||
|
||||
const { t } = useLingui();
|
||||
|
||||
const isEmailGroupFeatureEnabled = useIsFeatureEnabled(
|
||||
FeatureFlagKey.IS_EMAIL_GROUP_ENABLED,
|
||||
);
|
||||
|
||||
const apolloClient = useApolloClient();
|
||||
|
||||
const { data: unsubscribePreviewData } = useQuery<{
|
||||
unsubscribePagePreviewUrl: string;
|
||||
}>(GET_UNSUBSCRIBE_PAGE_PREVIEW_URL, {
|
||||
client: apolloClient,
|
||||
skip: !isEmailGroupFeatureEnabled,
|
||||
});
|
||||
|
||||
const unsubscribePageUrl = unsubscribePreviewData?.unsubscribePagePreviewUrl;
|
||||
|
||||
if (!isEmailGroupFeatureEnabled) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<SettingsPageLayout
|
||||
title={t`Communications`}
|
||||
links={[
|
||||
{
|
||||
children: t`Workspace`,
|
||||
href: getSettingsPath(SettingsPath.General),
|
||||
},
|
||||
{ children: t`Communications` },
|
||||
]}
|
||||
>
|
||||
<SettingsPageContainer>
|
||||
<TabList
|
||||
componentInstanceId={COMMUNICATIONS_TABS_INSTANCE_ID}
|
||||
tabs={[
|
||||
{ id: 'emails', title: t`Emails`, Icon: IconMail },
|
||||
{
|
||||
id: 'whatsapp',
|
||||
title: t`Whatsapp`,
|
||||
Icon: IconBrandWhatsapp,
|
||||
disabled: true,
|
||||
pill: t`Soon`,
|
||||
},
|
||||
{
|
||||
id: 'calls',
|
||||
title: t`Calls`,
|
||||
Icon: IconPhone,
|
||||
disabled: true,
|
||||
pill: t`Soon`,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
<Section>
|
||||
<SettingsDiscoveryHeroCard
|
||||
lightSrc={coverLight}
|
||||
darkSrc={coverDark}
|
||||
instanceIdPrefix="settings-communications-hero"
|
||||
tabs={[]}
|
||||
/>
|
||||
</Section>
|
||||
<SettingsWorkspaceEmailGroupSection />
|
||||
<SettingsWorkspaceUnsubscribeTopicSection />
|
||||
<Section>
|
||||
<H2Title
|
||||
title={t`Unsubscribe`}
|
||||
description={t`The page your users will get redirected to to unsubscribe from your emails`}
|
||||
/>
|
||||
<StyledCardLink
|
||||
href={unsubscribePageUrl}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<SettingsCard
|
||||
Icon={
|
||||
<IconMailX
|
||||
size={theme.icon.size.lg}
|
||||
stroke={theme.icon.stroke.md}
|
||||
/>
|
||||
}
|
||||
title={t`See unsubscribe page`}
|
||||
/>
|
||||
</StyledCardLink>
|
||||
</Section>
|
||||
</SettingsPageContainer>
|
||||
</SettingsPageLayout>
|
||||
);
|
||||
};
|
||||
+4
-4
@@ -50,7 +50,7 @@ export const SettingsWorkspaceNewUnsubscribeTopic = () => {
|
||||
unsubscribeTopicId,
|
||||
});
|
||||
} else {
|
||||
navigate(SettingsPath.WorkspaceEmail);
|
||||
navigate(SettingsPath.WorkspaceCommunications);
|
||||
}
|
||||
} catch {
|
||||
enqueueErrorSnackBar({
|
||||
@@ -80,8 +80,8 @@ export const SettingsWorkspaceNewUnsubscribeTopic = () => {
|
||||
href: getSettingsPath(SettingsPath.General),
|
||||
},
|
||||
{
|
||||
children: t`Email`,
|
||||
href: getSettingsPath(SettingsPath.WorkspaceEmail),
|
||||
children: t`Communications`,
|
||||
href: getSettingsPath(SettingsPath.WorkspaceCommunications),
|
||||
},
|
||||
{ children: t`New Unsubscribe Topic` },
|
||||
]}
|
||||
@@ -90,7 +90,7 @@ export const SettingsWorkspaceNewUnsubscribeTopic = () => {
|
||||
isSaveDisabled={!canSave}
|
||||
isCancelDisabled={loading}
|
||||
isLoading={loading}
|
||||
onCancel={() => navigate(SettingsPath.WorkspaceEmail)}
|
||||
onCancel={() => navigate(SettingsPath.WorkspaceCommunications)}
|
||||
onSave={handleSave}
|
||||
/>
|
||||
}
|
||||
+3
-3
@@ -104,7 +104,7 @@ export const SettingsWorkspaceUnsubscribeTopicDetail = () => {
|
||||
const handleDelete = async () => {
|
||||
try {
|
||||
await deleteUnsubscribeTopic(unsubscribeTopic.id);
|
||||
navigateSettings(SettingsPath.WorkspaceEmail);
|
||||
navigateSettings(SettingsPath.WorkspaceCommunications);
|
||||
} catch {
|
||||
enqueueErrorSnackBar({
|
||||
message: t`Failed to delete unsubscribe topic.`,
|
||||
@@ -123,8 +123,8 @@ export const SettingsWorkspaceUnsubscribeTopicDetail = () => {
|
||||
href: getSettingsPath(SettingsPath.General),
|
||||
},
|
||||
{
|
||||
children: t`Email`,
|
||||
href: getSettingsPath(SettingsPath.WorkspaceEmail),
|
||||
children: t`Communications`,
|
||||
href: getSettingsPath(SettingsPath.WorkspaceCommunications),
|
||||
},
|
||||
{ children: topicName },
|
||||
]}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 46 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 48 KiB |
@@ -1,82 +0,0 @@
|
||||
import { useLingui } from '@lingui/react/macro';
|
||||
|
||||
import { billingState } from '@/client-config/states/billingState';
|
||||
import { isEmailingDomainInDemoModeState } from '@/client-config/states/isEmailingDomainInDemoModeState';
|
||||
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
|
||||
import { SettingsOptionCardContentButton } from '@/settings/components/SettingsOptions/SettingsOptionCardContentButton';
|
||||
import { SettingsWorkspaceUnsubscribeTopicSection } from '@/settings/unsubscribe-topics/components/SettingsWorkspaceUnsubscribeTopicSection';
|
||||
import { SettingsWorkspaceEmailGroupSection } from '@/settings/workspace/components/SettingsWorkspaceEmailGroupSection';
|
||||
import { SettingsPageLayout } from '@/settings/components/layout/SettingsPageLayout';
|
||||
import { useAtomStateValue } from '@/ui/utilities/state/jotai/hooks/useAtomStateValue';
|
||||
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
|
||||
import { FeatureFlagKey, SettingsPath } from 'twenty-shared/types';
|
||||
import { getSettingsPath } from 'twenty-shared/utils';
|
||||
import { IconArrowUp, IconLock } from 'twenty-ui/icon';
|
||||
import { Button } from 'twenty-ui/input';
|
||||
import { Card } from 'twenty-ui/surfaces';
|
||||
import { themeCssVariables } from 'twenty-ui/theme-constants';
|
||||
import { useNavigateSettings } from '~/hooks/useNavigateSettings';
|
||||
|
||||
export const SettingsWorkspaceEmail = () => {
|
||||
const { t } = useLingui();
|
||||
const navigateSettings = useNavigateSettings();
|
||||
|
||||
const isEmailingDomainInDemoMode = useAtomStateValue(
|
||||
isEmailingDomainInDemoModeState,
|
||||
);
|
||||
const billing = useAtomStateValue(billingState);
|
||||
const isBillingEnabled = billing?.isBillingEnabled ?? false;
|
||||
const isEmailGroupFeatureEnabled = useIsFeatureEnabled(
|
||||
FeatureFlagKey.IS_EMAIL_GROUP_ENABLED,
|
||||
);
|
||||
|
||||
if (!isEmailGroupFeatureEnabled) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<SettingsPageLayout
|
||||
title={t`Email`}
|
||||
links={[
|
||||
{
|
||||
children: t`Workspace`,
|
||||
href: getSettingsPath(SettingsPath.General),
|
||||
},
|
||||
{ children: t`Email` },
|
||||
]}
|
||||
>
|
||||
<SettingsPageContainer>
|
||||
{isEmailingDomainInDemoMode && (
|
||||
<Card
|
||||
rounded
|
||||
backgroundColor={themeCssVariables.background.secondary}
|
||||
>
|
||||
<SettingsOptionCardContentButton
|
||||
Icon={IconLock}
|
||||
title={t`Emailing is in demo mode`}
|
||||
description={t`Emails are logged, not sent. Sending requires the AWS SES driver with an Enterprise license, or Twenty Cloud.`}
|
||||
Button={
|
||||
<Button
|
||||
title={t`Upgrade`}
|
||||
variant="primary"
|
||||
accent="blue"
|
||||
size="small"
|
||||
Icon={IconArrowUp}
|
||||
onClick={() =>
|
||||
navigateSettings(
|
||||
isBillingEnabled
|
||||
? SettingsPath.BillingPlans
|
||||
: SettingsPath.AdminPanelEnterprise,
|
||||
)
|
||||
}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</Card>
|
||||
)}
|
||||
<SettingsWorkspaceEmailGroupSection />
|
||||
<SettingsWorkspaceUnsubscribeTopicSection />
|
||||
</SettingsPageContainer>
|
||||
</SettingsPageLayout>
|
||||
);
|
||||
};
|
||||
@@ -28,7 +28,7 @@ export enum SettingsPath {
|
||||
General = 'general',
|
||||
Subdomain = 'general/subdomain',
|
||||
CustomDomain = 'general/custom-domain',
|
||||
WorkspaceEmail = 'email',
|
||||
WorkspaceCommunications = 'communications',
|
||||
EmailGroupChannelDetail = 'email/email-group/:messageChannelId',
|
||||
NewEmailGroupChannel = 'email/new-email-group',
|
||||
NewUnsubscribeTopic = 'email/new-unsubscribe-topic',
|
||||
|
||||
@@ -53,6 +53,7 @@ export {
|
||||
IconBrandLinkedin,
|
||||
IconBrandNpm,
|
||||
IconBrandOpenai,
|
||||
IconBrandWhatsapp,
|
||||
IconBrandX,
|
||||
IconBriefcase,
|
||||
IconBroadcast,
|
||||
@@ -101,6 +102,7 @@ export {
|
||||
IconColumnInsertRight,
|
||||
IconColumns,
|
||||
IconCommand,
|
||||
IconMessageCircle,
|
||||
IconMessageCircle as IconComment,
|
||||
IconCopy,
|
||||
IconCopyPlus,
|
||||
|
||||
@@ -108,6 +108,7 @@ export {
|
||||
IconBrandLinkedin,
|
||||
IconBrandNpm,
|
||||
IconBrandOpenai,
|
||||
IconBrandWhatsapp,
|
||||
IconBrandX,
|
||||
IconBriefcase,
|
||||
IconBroadcast,
|
||||
@@ -335,6 +336,7 @@ export {
|
||||
IconMathXy,
|
||||
IconMaximize,
|
||||
IconMessage,
|
||||
IconMessageCircle,
|
||||
IconMessageCirclePlus,
|
||||
IconMinus,
|
||||
IconMoneybag,
|
||||
|
||||
Reference in New Issue
Block a user