6446 improve information banner component to make it scale better (#6545)

Closes #6446
This commit is contained in:
bosiraphael
2024-08-05 16:00:52 +02:00
committed by GitHub
parent fa738ec373
commit 2073d8e6e1
16 changed files with 192 additions and 96 deletions

View File

@ -1,26 +0,0 @@
import { currentUserState } from '@/auth/states/currentUserState';
import { InformationBannerAccountToReconnect } from '@/information-banner/InformationBannerReconnectAccount';
import { useRecoilValue } from 'recoil';
export enum InformationBannerKeys {
ACCOUNTS_TO_RECONNECT = 'ACCOUNTS_TO_RECONNECT',
}
export const InformationBanner = () => {
const currentUser = useRecoilValue(currentUserState);
const userVars = currentUser?.userVars;
const firstAccountIdToReconnect =
userVars?.[InformationBannerKeys.ACCOUNTS_TO_RECONNECT]?.[0];
return (
<>
{firstAccountIdToReconnect && (
<InformationBannerAccountToReconnect
accountIdToReconnect={firstAccountIdToReconnect}
/>
)}
</>
);
};

View File

@ -1,38 +0,0 @@
import { ConnectedAccount } from '@/accounts/types/ConnectedAccount';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { useFindOneRecord } from '@/object-record/hooks/useFindOneRecord';
import { useTriggerGoogleApisOAuth } from '@/settings/accounts/hooks/useTriggerGoogleApisOAuth';
import { Button } from '@/ui/input/button/components/Button';
import { Banner, IconRefresh } from 'twenty-ui';
export const InformationBannerAccountToReconnect = ({
accountIdToReconnect,
}: {
accountIdToReconnect: string;
}) => {
const accountToReconnect = useFindOneRecord<ConnectedAccount>({
objectNameSingular: CoreObjectNameSingular.ConnectedAccount,
objectRecordId: accountIdToReconnect,
});
const { triggerGoogleApisOAuth } = useTriggerGoogleApisOAuth();
if (!accountToReconnect?.record) {
return null;
}
return (
<Banner>
Sync lost with mailbox {accountToReconnect?.record?.handle}. Please
reconnect for updates:
<Button
variant="secondary"
title="Reconnect"
Icon={IconRefresh}
size="small"
inverted
onClick={() => triggerGoogleApisOAuth()}
/>
</Banner>
);
};

View File

@ -0,0 +1,39 @@
import { Button } from '@/ui/input/button/components/Button';
import styled from '@emotion/styled';
import { Banner, IconComponent } from 'twenty-ui';
const StyledBanner = styled(Banner)`
position: absolute;
`;
const StyledText = styled.div`
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
`;
export const InformationBanner = ({
message,
buttonTitle,
buttonIcon,
buttonOnClick,
}: {
message: string;
buttonTitle: string;
buttonIcon?: IconComponent;
buttonOnClick: () => void;
}) => {
return (
<StyledBanner>
<StyledText>{message}</StyledText>
<Button
variant="secondary"
title={buttonTitle}
Icon={buttonIcon}
size="small"
inverted
onClick={buttonOnClick}
/>
</StyledBanner>
);
};

View File

@ -0,0 +1,21 @@
import { InformationBannerReconnectAccountEmailAliases } from '@/information-banner/components/reconnect-account/InformationBannerReconnectAccountEmailAliases';
import { InformationBannerReconnectAccountInsufficientPermissions } from '@/information-banner/components/reconnect-account/InformationBannerReconnectAccountInsufficientPermissions';
import styled from '@emotion/styled';
const StyledInformationBannerWrapper = styled.div`
height: 40px;
position: relative;
&:empty {
height: 0;
}
`;
export const InformationBannerWrapper = () => {
return (
<StyledInformationBannerWrapper>
<InformationBannerReconnectAccountInsufficientPermissions />
<InformationBannerReconnectAccountEmailAliases />
</StyledInformationBannerWrapper>
);
};

View File

@ -0,0 +1,26 @@
import { InformationBanner } from '@/information-banner/components/InformationBanner';
import { InformationBannerKeys } from '@/information-banner/enums/InformationBannerKeys.enum';
import { useAccountToReconnect } from '@/information-banner/hooks/useAccountToReconnect';
import { useTriggerGoogleApisOAuth } from '@/settings/accounts/hooks/useTriggerGoogleApisOAuth';
import { IconRefresh } from 'twenty-ui';
export const InformationBannerReconnectAccountEmailAliases = () => {
const { accountToReconnect } = useAccountToReconnect(
InformationBannerKeys.ACCOUNTS_TO_RECONNECT_EMAIL_ALIASES,
);
const { triggerGoogleApisOAuth } = useTriggerGoogleApisOAuth();
if (!accountToReconnect) {
return null;
}
return (
<InformationBanner
message={`Please reconnect your mailbox ${accountToReconnect?.handle} to update your email aliases:`}
buttonTitle="Reconnect"
buttonIcon={IconRefresh}
buttonOnClick={() => triggerGoogleApisOAuth()}
/>
);
};

View File

@ -0,0 +1,27 @@
import { InformationBanner } from '@/information-banner/components/InformationBanner';
import { InformationBannerKeys } from '@/information-banner/enums/InformationBannerKeys.enum';
import { useAccountToReconnect } from '@/information-banner/hooks/useAccountToReconnect';
import { useTriggerGoogleApisOAuth } from '@/settings/accounts/hooks/useTriggerGoogleApisOAuth';
import { IconRefresh } from 'twenty-ui';
export const InformationBannerReconnectAccountInsufficientPermissions = () => {
const { accountToReconnect } = useAccountToReconnect(
InformationBannerKeys.ACCOUNTS_TO_RECONNECT_INSUFFICIENT_PERMISSIONS,
);
const { triggerGoogleApisOAuth } = useTriggerGoogleApisOAuth();
if (!accountToReconnect) {
return null;
}
return (
<InformationBanner
message={`Sync lost with mailbox ${accountToReconnect?.handle}. Please
reconnect for updates:`}
buttonTitle="Reconnect"
buttonIcon={IconRefresh}
buttonOnClick={() => triggerGoogleApisOAuth()}
/>
);
};

View File

@ -0,0 +1,4 @@
export enum InformationBannerKeys {
ACCOUNTS_TO_RECONNECT_INSUFFICIENT_PERMISSIONS = 'ACCOUNTS_TO_RECONNECT_INSUFFICIENT_PERMISSIONS',
ACCOUNTS_TO_RECONNECT_EMAIL_ALIASES = 'ACCOUNTS_TO_RECONNECT_EMAIL_ALIASES',
}

View File

@ -0,0 +1,24 @@
import { ConnectedAccount } from '@/accounts/types/ConnectedAccount';
import { currentUserState } from '@/auth/states/currentUserState';
import { InformationBannerKeys } from '@/information-banner/enums/InformationBannerKeys.enum';
import { CoreObjectNameSingular } from '@/object-metadata/types/CoreObjectNameSingular';
import { useFindOneRecord } from '@/object-record/hooks/useFindOneRecord';
import { useRecoilValue } from 'recoil';
export const useAccountToReconnect = (key: InformationBannerKeys) => {
const currentUser = useRecoilValue(currentUserState);
const userVars = currentUser?.userVars;
const firstAccountIdToReconnect = userVars?.[key]?.[0];
const accountToReconnect = useFindOneRecord<ConnectedAccount>({
objectNameSingular: CoreObjectNameSingular.ConnectedAccount,
objectRecordId: firstAccountIdToReconnect,
skip: !firstAccountIdToReconnect,
});
return {
accountToReconnect: accountToReconnect?.record,
};
};

View File

@ -1,7 +1,6 @@
import styled from '@emotion/styled';
import { useRecoilCallback, useRecoilState, useSetRecoilState } from 'recoil';
import { InformationBanner } from '@/information-banner/InformationBanner';
import { useColumnDefinitionsFromFieldMetadata } from '@/object-metadata/hooks/useColumnDefinitionsFromFieldMetadata';
import { useObjectMetadataItem } from '@/object-metadata/hooks/useObjectMetadataItem';
import { useObjectNameSingularFromPlural } from '@/object-metadata/hooks/useObjectNameSingularFromPlural';
@ -21,6 +20,7 @@ import { recordIndexKanbanFieldMetadataIdState } from '@/object-record/record-in
import { recordIndexSortsState } from '@/object-record/record-index/states/recordIndexSortsState';
import { recordIndexViewTypeState } from '@/object-record/record-index/states/recordIndexViewTypeState';
import { InformationBannerWrapper } from '@/information-banner/components/InformationBannerWrapper';
import { useHandleIndexIdentifierClick } from '@/object-record/record-index/hooks/useHandleIndexIdentifierClick';
import { RecordFieldValueSelectorContextProvider } from '@/object-record/record-store/contexts/RecordFieldValueSelectorContext';
import { useRecordTable } from '@/object-record/record-table/hooks/useRecordTable';
@ -42,7 +42,7 @@ const StyledContainer = styled.div`
`;
const StyledContainerWithPadding = styled.div<{ fullHeight?: boolean }>`
min-height: ${({ fullHeight }) => (fullHeight ? '100%' : 'auto')};
height: ${({ fullHeight }) => (fullHeight ? '100%' : 'auto')};
padding-left: ${({ theme }) => theme.table.horizontalCellPadding};
`;
@ -126,7 +126,7 @@ export const RecordIndexContainer = ({
return (
<StyledContainer>
<InformationBanner />
<InformationBannerWrapper />
<RecordFieldValueSelectorContextProvider>
<SpreadsheetImportProvider>
<StyledContainerWithPadding>

View File

@ -4,7 +4,7 @@ import { IconComponent } from 'twenty-ui';
import { useIsMobile } from '@/ui/utilities/responsive/hooks/useIsMobile';
import { InformationBanner } from '@/information-banner/InformationBanner';
import { InformationBannerWrapper } from '@/information-banner/components/InformationBannerWrapper';
import { PageBody } from './PageBody';
import { PageHeader } from './PageHeader';
@ -34,7 +34,7 @@ export const SubMenuTopBarContainer = ({
<StyledContainer isMobile={isMobile} className={className}>
{isMobile && <PageHeader title={title} Icon={Icon} />}
<PageBody>
<InformationBanner />
<InformationBannerWrapper />
{children}
</PageBody>
</StyledContainer>

View File

@ -85,7 +85,8 @@ export class UserResolver {
const userVarAllowList = [
'SYNC_EMAIL_ONBOARDING_STEP',
'ACCOUNTS_TO_RECONNECT',
'ACCOUNTS_TO_RECONNECT_INSUFFICIENT_PERMISSIONS',
'ACCOUNTS_TO_RECONNECT_EMAIL_ALIASES',
];
const filteredMap = new Map(

View File

@ -11,9 +11,9 @@ import {
CalendarChannelWorkspaceEntity,
} from 'src/modules/calendar/common/standard-objects/calendar-channel.workspace-entity';
import {
ConnectedAccountKeyValueType,
ConnectedAccountKeys,
} from 'src/modules/connected-account/types/connected-account-key-value.type';
AccountsToReconnectKeyValueType,
AccountsToReconnectKeys,
} from 'src/modules/connected-account/types/accounts-to-reconnect-key-value.type';
@Injectable()
export class CalendarChannelSyncStatusService {
@ -21,7 +21,7 @@ export class CalendarChannelSyncStatusService {
private readonly twentyORMManager: TwentyORMManager,
@InjectCacheStorage(CacheStorageNamespace.Calendar)
private readonly cacheStorage: CacheStorageService,
private readonly userVarsService: UserVarsService<ConnectedAccountKeyValueType>,
private readonly userVarsService: UserVarsService<AccountsToReconnectKeyValueType>,
) {}
public async scheduleFullCalendarEventListFetch(calendarChannelId: string) {
@ -198,7 +198,7 @@ export class CalendarChannelSyncStatusService {
(await this.userVarsService.get({
userId,
workspaceId,
key: ConnectedAccountKeys.ACCOUNTS_TO_RECONNECT,
key: AccountsToReconnectKeys.ACCOUNTS_TO_RECONNECT_INSUFFICIENT_PERMISSIONS,
})) ?? [];
if (accountsToReconnect.includes(connectedAccountId)) {
@ -210,7 +210,7 @@ export class CalendarChannelSyncStatusService {
await this.userVarsService.set({
userId,
workspaceId,
key: ConnectedAccountKeys.ACCOUNTS_TO_RECONNECT,
key: AccountsToReconnectKeys.ACCOUNTS_TO_RECONNECT_INSUFFICIENT_PERMISSIONS,
value: accountsToReconnect,
});
}

View File

@ -2,25 +2,41 @@ import { Injectable } from '@nestjs/common';
import { UserVarsService } from 'src/engine/core-modules/user/user-vars/services/user-vars.service';
import {
ConnectedAccountKeys,
ConnectedAccountKeyValueType,
} from 'src/modules/connected-account/types/connected-account-key-value.type';
AccountsToReconnectKeyValueType,
AccountsToReconnectKeys,
} from 'src/modules/connected-account/types/accounts-to-reconnect-key-value.type';
@Injectable()
export class AccountsToReconnectService {
constructor(
private readonly userVarsService: UserVarsService<ConnectedAccountKeyValueType>,
private readonly userVarsService: UserVarsService<AccountsToReconnectKeyValueType>,
) {}
public async removeAccountToReconnect(
userId: string,
workspaceId: string,
connectedAccountId: string,
) {
for (const key of Object.values(AccountsToReconnectKeys)) {
await this.removeAccountToReconnectByKey(
key,
userId,
workspaceId,
connectedAccountId,
);
}
}
private async removeAccountToReconnectByKey(
key: AccountsToReconnectKeys,
userId: string,
workspaceId: string,
connectedAccountId: string,
) {
const accountsToReconnect = await this.userVarsService.get({
userId,
workspaceId,
key: ConnectedAccountKeys.ACCOUNTS_TO_RECONNECT,
key,
});
if (!accountsToReconnect) {
@ -35,7 +51,7 @@ export class AccountsToReconnectService {
await this.userVarsService.delete({
userId,
workspaceId,
key: ConnectedAccountKeys.ACCOUNTS_TO_RECONNECT,
key,
});
return;
@ -44,7 +60,7 @@ export class AccountsToReconnectService {
await this.userVarsService.set({
userId,
workspaceId,
key: ConnectedAccountKeys.ACCOUNTS_TO_RECONNECT,
key,
value: updatedAccountsToReconnect,
});
}

View File

@ -0,0 +1,9 @@
export enum AccountsToReconnectKeys {
ACCOUNTS_TO_RECONNECT_INSUFFICIENT_PERMISSIONS = 'ACCOUNTS_TO_RECONNECT_INSUFFICIENT_PERMISSIONS',
ACCOUNTS_TO_RECONNECT_EMAIL_ALIASES = 'ACCOUNTS_TO_RECONNECT_EMAIL_ALIASES',
}
export type AccountsToReconnectKeyValueType = {
[AccountsToReconnectKeys.ACCOUNTS_TO_RECONNECT_INSUFFICIENT_PERMISSIONS]: string[];
[AccountsToReconnectKeys.ACCOUNTS_TO_RECONNECT_EMAIL_ALIASES]: string[];
};

View File

@ -1,7 +0,0 @@
export enum ConnectedAccountKeys {
ACCOUNTS_TO_RECONNECT = 'ACCOUNTS_TO_RECONNECT',
}
export type ConnectedAccountKeyValueType = {
[ConnectedAccountKeys.ACCOUNTS_TO_RECONNECT]: string[];
};

View File

@ -7,9 +7,9 @@ import { CacheStorageNamespace } from 'src/engine/integrations/cache-storage/typ
import { InjectObjectMetadataRepository } from 'src/engine/object-metadata-repository/object-metadata-repository.decorator';
import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager';
import {
ConnectedAccountKeys,
ConnectedAccountKeyValueType,
} from 'src/modules/connected-account/types/connected-account-key-value.type';
AccountsToReconnectKeyValueType,
AccountsToReconnectKeys,
} from 'src/modules/connected-account/types/accounts-to-reconnect-key-value.type';
import { MessageChannelRepository } from 'src/modules/messaging/common/repositories/message-channel.repository';
import {
MessageChannelSyncStage,
@ -24,7 +24,7 @@ export class MessagingChannelSyncStatusService {
private readonly messageChannelRepository: MessageChannelRepository,
@InjectCacheStorage(CacheStorageNamespace.Messaging)
private readonly cacheStorage: CacheStorageService,
private readonly userVarsService: UserVarsService<ConnectedAccountKeyValueType>,
private readonly userVarsService: UserVarsService<AccountsToReconnectKeyValueType>,
private readonly twentyORMManager: TwentyORMManager,
) {}
@ -203,7 +203,7 @@ export class MessagingChannelSyncStatusService {
(await this.userVarsService.get({
userId,
workspaceId,
key: ConnectedAccountKeys.ACCOUNTS_TO_RECONNECT,
key: AccountsToReconnectKeys.ACCOUNTS_TO_RECONNECT_INSUFFICIENT_PERMISSIONS,
})) ?? [];
if (accountsToReconnect.includes(connectedAccountId)) {
@ -215,7 +215,7 @@ export class MessagingChannelSyncStatusService {
await this.userVarsService.set({
userId,
workspaceId,
key: ConnectedAccountKeys.ACCOUNTS_TO_RECONNECT,
key: AccountsToReconnectKeys.ACCOUNTS_TO_RECONNECT_INSUFFICIENT_PERMISSIONS,
value: accountsToReconnect,
});
}