3815 blocklist connect frontend (#3930)

* wip

* wip

* move blocklist to connectedAccount

* wip

* format date

* fix styling

* renaming

* fix imports

* fix imports

* Rename BlockListItem.ts to BlocklistItem.ts

* Add IS_BLOCKLIST_ENABLED feature flag and remove IS_MESSAGING_ENABLED gate at model creation

* hide blocklist if feature flag is disabled
This commit is contained in:
bosiraphael
2024-02-15 17:18:04 +01:00
committed by GitHub
parent 4b3eeac333
commit 0b93a6785b
22 changed files with 91 additions and 129 deletions

View File

@ -1,5 +0,0 @@
export type BlockedEmail = {
id: string;
email: string;
blocked_at: string;
};

View File

@ -0,0 +1,6 @@
export type BlocklistItem = {
id: string;
handle: string;
workspaceMemberId: string;
createdAt: string;
};

View File

@ -3,6 +3,7 @@ export enum CoreObjectNameSingular {
ActivityTarget = 'activityTarget',
ApiKey = 'apiKey',
Attachment = 'attachment',
Blocklist = 'blocklist',
Comment = 'comment',
Company = 'company',
ConenctedAccount = 'conenctedAccount',

View File

@ -8,7 +8,6 @@ import { TextInput } from '@/ui/input/components/TextInput';
const StyledContainer = styled.div`
display: flex;
flex-direction: row;
margin-bottom: 16px;
`;
const StyledLinkContainer = styled.div`

View File

@ -1,32 +1,43 @@
import { useState } from 'react';
import { v4 } from 'uuid';
import { useRecoilValue } from 'recoil';
import { BlocklistItem } from '@/accounts/types/BlocklistItem';
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { useCreateOneRecord } from '@/object-record/hooks/useCreateOneRecord';
import { useDeleteOneRecord } from '@/object-record/hooks/useDeleteOneRecord';
import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords';
import { SettingsAccountsEmailsBlocklistInput } from '@/settings/accounts/components/SettingsAccountsEmailsBlocklistInput';
import { SettingsAccountsEmailsBlocklistTable } from '@/settings/accounts/components/SettingsAccountsEmailsBlocklistTable';
import { H2Title } from '@/ui/display/typography/components/H2Title';
import { Section } from '@/ui/layout/section/components/Section';
import { mockedBlockedEmailList } from '~/testing/mock-data/accounts';
import { formatDate } from '~/utils/date-utils';
export const SettingsAccountsEmailsBlocklistSection = () => {
const [blockedEmailList, setBlockedEmailList] = useState(
mockedBlockedEmailList,
);
const currentWorkspaceMember = useRecoilValue(currentWorkspaceMemberState);
const handleBlockedEmailRemove = (id: string) =>
setBlockedEmailList((previousBlockedEmailList) =>
previousBlockedEmailList.filter((blockedEmail) => blockedEmail.id !== id),
);
const { records: blocklist } = useFindManyRecords<BlocklistItem>({
objectNameSingular: CoreObjectNameSingular.Blocklist,
});
const { createOneRecord: createBlocklistItem } =
useCreateOneRecord<BlocklistItem>({
objectNameSingular: CoreObjectNameSingular.Blocklist,
});
const { deleteOneRecord: deleteBlocklistItem } = useDeleteOneRecord({
objectNameSingular: CoreObjectNameSingular.Blocklist,
});
const handleBlockedEmailRemove = (id: string) => {
deleteBlocklistItem(id);
};
const updateBlockedEmailList = (handle: string) => {
createBlocklistItem({
handle,
workspaceMemberId: currentWorkspaceMember?.id,
});
};
const updateBlockedEmailList = (email: string) =>
setBlockedEmailList((prevState) => [
...prevState,
{
id: v4(),
email: email,
blocked_at: formatDate(new Date(), 'dd/LL/yyyy'),
},
]);
return (
<Section>
<H2Title
@ -37,7 +48,7 @@ export const SettingsAccountsEmailsBlocklistSection = () => {
updateBlockedEmailList={updateBlockedEmailList}
/>
<SettingsAccountsEmailsBlocklistTable
blockedEmailList={blockedEmailList}
blocklist={blocklist}
handleBlockedEmailRemove={handleBlockedEmailRemove}
/>
</Section>

View File

@ -1,6 +1,6 @@
import styled from '@emotion/styled';
import { BlockedEmail } from '@/accounts/types/BlockedEmail';
import { BlocklistItem } from '@/accounts/types/BlocklistItem';
import { SettingsAccountsEmailsBlocklistTableRow } from '@/settings/accounts/components/SettingsAccountsEmailsBlocklistTableRow';
import { Table } from '@/ui/layout/table/components/Table';
import { TableBody } from '@/ui/layout/table/components/TableBody';
@ -8,33 +8,42 @@ import { TableHeader } from '@/ui/layout/table/components/TableHeader';
import { TableRow } from '@/ui/layout/table/components/TableRow';
type SettingsAccountsEmailsBlocklistTableProps = {
blockedEmailList: BlockedEmail[];
blocklist: BlocklistItem[];
handleBlockedEmailRemove: (id: string) => void;
};
const StyledTable = styled(Table)`
margin-top: ${({ theme }) => theme.spacing(4)};
`;
const StyledTableBody = styled(TableBody)`
border-bottom: 1px solid ${({ theme }) => theme.border.color.light};
`;
export const SettingsAccountsEmailsBlocklistTable = ({
blockedEmailList,
blocklist,
handleBlockedEmailRemove,
}: SettingsAccountsEmailsBlocklistTableProps) => {
return (
<Table>
<TableRow>
<TableHeader>Email/Domain</TableHeader>
<TableHeader>Added to blocklist</TableHeader>
<TableHeader></TableHeader>
</TableRow>
<StyledTableBody>
{blockedEmailList.map((blockedEmail) => (
<SettingsAccountsEmailsBlocklistTableRow
key={blockedEmail.id}
blockedEmail={blockedEmail}
onRemove={handleBlockedEmailRemove}
/>
))}
</StyledTableBody>
</Table>
<>
{blocklist.length > 0 && (
<StyledTable>
<TableRow>
<TableHeader>Email/Domain</TableHeader>
<TableHeader>Added to blocklist</TableHeader>
<TableHeader></TableHeader>
</TableRow>
<StyledTableBody>
{blocklist.map((blocklistItem) => (
<SettingsAccountsEmailsBlocklistTableRow
key={blocklistItem.id}
blocklistItem={blocklistItem}
onRemove={handleBlockedEmailRemove}
/>
))}
</StyledTableBody>
</StyledTable>
)}
</>
);
};

View File

@ -1,26 +1,29 @@
import { BlockedEmail } from '@/accounts/types/BlockedEmail';
import { BlocklistItem } from '@/accounts/types/BlocklistItem';
import { IconX } from '@/ui/display/icon';
import { IconButton } from '@/ui/input/button/components/IconButton';
import { TableCell } from '@/ui/layout/table/components/TableCell';
import { TableRow } from '@/ui/layout/table/components/TableRow';
import { formatToHumanReadableDate } from '~/utils';
type SettingsAccountsEmailsBlocklistTableRowProps = {
blockedEmail: BlockedEmail;
blocklistItem: BlocklistItem;
onRemove: (id: string) => void;
};
export const SettingsAccountsEmailsBlocklistTableRow = ({
blockedEmail,
blocklistItem,
onRemove,
}: SettingsAccountsEmailsBlocklistTableRowProps) => {
return (
<TableRow key={blockedEmail.id}>
<TableCell>{blockedEmail.email}</TableCell>
<TableCell>{blockedEmail.blocked_at}</TableCell>
<TableRow key={blocklistItem.id}>
<TableCell>{blocklistItem.handle}</TableCell>
<TableCell>
{formatToHumanReadableDate(blocklistItem.createdAt)}
</TableCell>
<TableCell align="right">
<IconButton
onClick={() => {
onRemove(blockedEmail.id);
onRemove(blocklistItem.id);
}}
variant="tertiary"
size="small"

View File

@ -1,5 +1,6 @@
export type FeatureFlagKey =
| 'IS_MESSAGING_ENABLED'
| 'IS_BLOCKLIST_ENABLED'
| 'IS_INTEGRATIONS_ENABLED'
| 'IS_QUICK_ACTIONS_ENABLED'
| 'IS_NEW_RECORD_BOARD_ENABLED';

View File

@ -4,11 +4,13 @@ import { ConnectedAccount } from '@/accounts/types/ConnectedAccount';
import { currentWorkspaceMemberState } from '@/auth/states/currentWorkspaceMemberState';
import { useFindManyRecords } from '@/object-record/hooks/useFindManyRecords';
import { SettingsAccountsConnectedAccountsSection } from '@/settings/accounts/components/SettingsAccountsConnectedAccountsSection';
import { SettingsAccountsEmailsBlocklistSection } from '@/settings/accounts/components/SettingsAccountsEmailsBlocklistSection';
import { SettingsAccountsSettingsSection } from '@/settings/accounts/components/SettingsAccountsSettingsSection';
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
import { IconSettings } from '@/ui/display/icon';
import { SubMenuTopBarContainer } from '@/ui/layout/page/SubMenuTopBarContainer';
import { Breadcrumb } from '@/ui/navigation/bread-crumb/components/Breadcrumb';
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
import { SettingsAccountLoader } from '~/pages/settings/accounts/SettingsAccountLoader';
export const SettingsAccounts = () => {
@ -23,6 +25,8 @@ export const SettingsAccounts = () => {
},
});
const isBlocklistEnabled = useIsFeatureEnabled('IS_BLOCKLIST_ENABLED');
return (
<SubMenuTopBarContainer Icon={IconSettings} title="Settings">
<SettingsPageContainer
@ -39,6 +43,7 @@ export const SettingsAccounts = () => {
) : (
<>
<SettingsAccountsConnectedAccountsSection accounts={accounts} />
{isBlocklistEnabled && <SettingsAccountsEmailsBlocklistSection />}
<SettingsAccountsSettingsSection />
</>
)}

View File

@ -1,4 +1,3 @@
import { SettingsAccountsEmailsBlocklistSection } from '@/settings/accounts/components/SettingsAccountsEmailsBlocklistSection';
import { SettingsAccountsEmailsSyncSection } from '@/settings/accounts/components/SettingsAccountsEmailsSyncSection';
import { SettingsPageContainer } from '@/settings/components/SettingsPageContainer';
import { IconSettings } from '@/ui/display/icon';
@ -15,7 +14,6 @@ export const SettingsAccountsEmails = () => (
]}
/>
<SettingsAccountsEmailsSyncSection />
<SettingsAccountsEmailsBlocklistSection />
</SettingsPageContainer>
</SubMenuTopBarContainer>
);

View File

@ -1,38 +0,0 @@
import { BlockedEmail } from '@/accounts/types/BlockedEmail';
import { MessageChannel } from '@/accounts/types/MessageChannel';
import { InboxSettingsVisibilityValue } from '@/settings/accounts/components/SettingsAccountsInboxSettingsVisibilitySection';
export const mockedAccounts: MessageChannel[] = [
{
handle: 'thomas@twenty.com',
isSynced: true,
isContactAutoCreationEnabled: true,
id: '0794b782-f52e-48c3-977e-b0f57f90de24',
visibility: InboxSettingsVisibilityValue.Everything,
},
{
handle: 'thomas@twenty.dev',
isSynced: false,
isContactAutoCreationEnabled: true,
id: 'dc66a7ec-56b2-425b-a8e8-26ff0396c3aa',
visibility: InboxSettingsVisibilityValue.Metadata,
},
];
export const mockedBlockedEmailList: BlockedEmail[] = [
{
email: 'thomas@twenty.com',
id: '9594b782-232e-48c3-977e-b0f57f90de24',
blocked_at: '12/06/2023',
},
{
email: 'tim@apple.com',
id: 'ac64a7ec-76b2-325b-a8e8-28ff0396c3aa',
blocked_at: '11/06/2023',
},
{
email: '@microsoft.com',
id: 'ac6445ec-76b2-325b-58e8-28340396c3ff',
blocked_at: '04/06/2023',
},
];

View File

@ -16,6 +16,7 @@ import { Workspace } from 'src/core/workspace/workspace.entity';
export enum FeatureFlagKeys {
IsIntegrationsEnabled = 'IS_INTEGRATIONS_ENABLED',
IsMessagingEnabled = 'IS_MESSAGING_ENABLED',
IsBlocklistEnabled = 'IS_BLOCKLIST_ENABLED',
IsWorkspaceCleanable = 'IS_WORKSPACE_CLEANABLE',
IsNewRecordBoardEnabled = 'IS_NEW_RECORD_BOARD_ENABLED',
}

View File

@ -20,6 +20,11 @@ export const seedFeatureFlags = async (
workspaceId: workspaceId,
value: true,
},
{
key: FeatureFlagKeys.IsBlocklistEnabled,
workspaceId: workspaceId,
value: true,
},
{
key: FeatureFlagKeys.IsWorkspaceCleanable,
workspaceId: workspaceId,

View File

@ -14,7 +14,7 @@ import { WorkspaceMemberObjectMetadata } from 'src/workspace/workspace-sync-meta
icon: 'IconForbid2',
})
@Gate({
featureFlag: 'IS_MESSAGING_ENABLED',
featureFlag: 'IS_BLOCKLIST_ENABLED',
})
@IsSystem()
export class BlocklistObjectMetadata extends BaseObjectMetadata {

View File

@ -1,7 +1,6 @@
import { FieldMetadataType } from 'src/metadata/field-metadata/field-metadata.entity';
import { RelationMetadataType } from 'src/metadata/relation-metadata/relation-metadata.entity';
import { FieldMetadata } from 'src/workspace/workspace-sync-metadata/decorators/field-metadata.decorator';
import { Gate } from 'src/workspace/workspace-sync-metadata/decorators/gate.decorator';
import { IsNullable } from 'src/workspace/workspace-sync-metadata/decorators/is-nullable.decorator';
import { IsSystem } from 'src/workspace/workspace-sync-metadata/decorators/is-system.decorator';
import { ObjectMetadata } from 'src/workspace/workspace-sync-metadata/decorators/object-metadata.decorator';
@ -17,9 +16,6 @@ import { WorkspaceMemberObjectMetadata } from 'src/workspace/workspace-sync-meta
description: 'A connected account',
icon: 'IconAt',
})
@Gate({
featureFlag: 'IS_MESSAGING_ENABLED',
})
@IsSystem()
export class ConnectedAccountObjectMetadata extends BaseObjectMetadata {
@FieldMetadata({

View File

@ -1,6 +1,5 @@
import { FieldMetadataType } from 'src/metadata/field-metadata/field-metadata.entity';
import { FieldMetadata } from 'src/workspace/workspace-sync-metadata/decorators/field-metadata.decorator';
import { Gate } from 'src/workspace/workspace-sync-metadata/decorators/gate.decorator';
import { IsNullable } from 'src/workspace/workspace-sync-metadata/decorators/is-nullable.decorator';
import { IsSystem } from 'src/workspace/workspace-sync-metadata/decorators/is-system.decorator';
import { ObjectMetadata } from 'src/workspace/workspace-sync-metadata/decorators/object-metadata.decorator';
@ -16,9 +15,6 @@ import { MessageObjectMetadata } from 'src/workspace/workspace-sync-metadata/sta
description: 'Message Synced with a Message Channel',
icon: 'IconMessage',
})
@Gate({
featureFlag: 'IS_MESSAGING_ENABLED',
})
@IsSystem()
export class MessageChannelMessageAssociationObjectMetadata extends BaseObjectMetadata {
@FieldMetadata({

View File

@ -1,7 +1,6 @@
import { FieldMetadataType } from 'src/metadata/field-metadata/field-metadata.entity';
import { RelationMetadataType } from 'src/metadata/relation-metadata/relation-metadata.entity';
import { FieldMetadata } from 'src/workspace/workspace-sync-metadata/decorators/field-metadata.decorator';
import { Gate } from 'src/workspace/workspace-sync-metadata/decorators/gate.decorator';
import { IsNullable } from 'src/workspace/workspace-sync-metadata/decorators/is-nullable.decorator';
import { IsSystem } from 'src/workspace/workspace-sync-metadata/decorators/is-system.decorator';
import { ObjectMetadata } from 'src/workspace/workspace-sync-metadata/decorators/object-metadata.decorator';
@ -17,9 +16,6 @@ import { MessageChannelMessageAssociationObjectMetadata } from 'src/workspace/wo
description: 'Message Channels',
icon: 'IconMessage',
})
@Gate({
featureFlag: 'IS_MESSAGING_ENABLED',
})
@IsSystem()
export class MessageChannelObjectMetadata extends BaseObjectMetadata {
@FieldMetadata({

View File

@ -1,6 +1,5 @@
import { FieldMetadataType } from 'src/metadata/field-metadata/field-metadata.entity';
import { FieldMetadata } from 'src/workspace/workspace-sync-metadata/decorators/field-metadata.decorator';
import { Gate } from 'src/workspace/workspace-sync-metadata/decorators/gate.decorator';
import { IsNullable } from 'src/workspace/workspace-sync-metadata/decorators/is-nullable.decorator';
import { IsSystem } from 'src/workspace/workspace-sync-metadata/decorators/is-system.decorator';
import { ObjectMetadata } from 'src/workspace/workspace-sync-metadata/decorators/object-metadata.decorator';
@ -16,9 +15,6 @@ import { WorkspaceMemberObjectMetadata } from 'src/workspace/workspace-sync-meta
description: 'Message Participants',
icon: 'IconUserCircle',
})
@Gate({
featureFlag: 'IS_MESSAGING_ENABLED',
})
@IsSystem()
export class MessageParticipantObjectMetadata extends BaseObjectMetadata {
@FieldMetadata({

View File

@ -1,7 +1,6 @@
import { FieldMetadataType } from 'src/metadata/field-metadata/field-metadata.entity';
import { RelationMetadataType } from 'src/metadata/relation-metadata/relation-metadata.entity';
import { FieldMetadata } from 'src/workspace/workspace-sync-metadata/decorators/field-metadata.decorator';
import { Gate } from 'src/workspace/workspace-sync-metadata/decorators/gate.decorator';
import { IsNullable } from 'src/workspace/workspace-sync-metadata/decorators/is-nullable.decorator';
import { IsSystem } from 'src/workspace/workspace-sync-metadata/decorators/is-system.decorator';
import { ObjectMetadata } from 'src/workspace/workspace-sync-metadata/decorators/object-metadata.decorator';
@ -17,9 +16,6 @@ import { MessageObjectMetadata } from 'src/workspace/workspace-sync-metadata/sta
description: 'Message Thread',
icon: 'IconMessage',
})
@Gate({
featureFlag: 'IS_MESSAGING_ENABLED',
})
@IsSystem()
export class MessageThreadObjectMetadata extends BaseObjectMetadata {
@FieldMetadata({

View File

@ -1,7 +1,6 @@
import { FieldMetadataType } from 'src/metadata/field-metadata/field-metadata.entity';
import { RelationMetadataType } from 'src/metadata/relation-metadata/relation-metadata.entity';
import { FieldMetadata } from 'src/workspace/workspace-sync-metadata/decorators/field-metadata.decorator';
import { Gate } from 'src/workspace/workspace-sync-metadata/decorators/gate.decorator';
import { IsNullable } from 'src/workspace/workspace-sync-metadata/decorators/is-nullable.decorator';
import { IsSystem } from 'src/workspace/workspace-sync-metadata/decorators/is-system.decorator';
import { ObjectMetadata } from 'src/workspace/workspace-sync-metadata/decorators/object-metadata.decorator';
@ -18,9 +17,6 @@ import { MessageThreadObjectMetadata } from 'src/workspace/workspace-sync-metada
description: 'Message',
icon: 'IconMessage',
})
@Gate({
featureFlag: 'IS_MESSAGING_ENABLED',
})
@IsSystem()
export class MessageObjectMetadata extends BaseObjectMetadata {
@FieldMetadata({

View File

@ -3,7 +3,6 @@ import { LinkMetadata } from 'src/metadata/field-metadata/composite-types/link.c
import { FieldMetadataType } from 'src/metadata/field-metadata/field-metadata.entity';
import { RelationMetadataType } from 'src/metadata/relation-metadata/relation-metadata.entity';
import { FieldMetadata } from 'src/workspace/workspace-sync-metadata/decorators/field-metadata.decorator';
import { Gate } from 'src/workspace/workspace-sync-metadata/decorators/gate.decorator';
import { IsNullable } from 'src/workspace/workspace-sync-metadata/decorators/is-nullable.decorator';
import { IsSystem } from 'src/workspace/workspace-sync-metadata/decorators/is-system.decorator';
import { ObjectMetadata } from 'src/workspace/workspace-sync-metadata/decorators/object-metadata.decorator';
@ -177,9 +176,6 @@ export class PersonObjectMetadata extends BaseObjectMetadata {
objectName: 'messageParticipant',
inverseSideFieldName: 'person',
})
@Gate({
featureFlag: 'IS_MESSAGING_ENABLED',
})
@IsNullable()
messageParticipants: MessageParticipantObjectMetadata[];
}

View File

@ -171,9 +171,6 @@ export class WorkspaceMemberObjectMetadata extends BaseObjectMetadata {
objectName: 'connectedAccount',
inverseSideFieldName: 'accountOwner',
})
@Gate({
featureFlag: 'IS_MESSAGING_ENABLED',
})
@IsNullable()
connectedAccounts: ConnectedAccountObjectMetadata[];
@ -188,9 +185,6 @@ export class WorkspaceMemberObjectMetadata extends BaseObjectMetadata {
objectName: 'messageParticipant',
inverseSideFieldName: 'workspaceMember',
})
@Gate({
featureFlag: 'IS_MESSAGING_ENABLED',
})
@IsNullable()
messageParticipants: MessageParticipantObjectMetadata[];
@ -206,7 +200,7 @@ export class WorkspaceMemberObjectMetadata extends BaseObjectMetadata {
inverseSideFieldName: 'workspaceMember',
})
@Gate({
featureFlag: 'IS_MESSAGING_ENABLED',
featureFlag: 'IS_BLOCKLIST_ENABLED',
})
@IsNullable()
blocklist: BlocklistObjectMetadata[];