Update Connected Accounts Design (#13332)

/closes #13328

---------

Co-authored-by: Félix Malfait <felix.malfait@gmail.com>
Co-authored-by: Félix Malfait <felix@twenty.com>
This commit is contained in:
neo773
2025-07-25 16:48:02 +05:30
committed by GitHub
parent 79992b53d1
commit d4ca63dbb7
8 changed files with 153 additions and 70 deletions

View File

@ -1,40 +1,65 @@
import { ConnectedAccount } from '@/accounts/types/ConnectedAccount'; import { ConnectedAccount } from '@/accounts/types/ConnectedAccount';
import { SettingsAccountsListEmptyStateCard } from '@/settings/accounts/components/SettingsAccountsListEmptyStateCard'; import { SettingsAccountsListEmptyStateCard } from '@/settings/accounts/components/SettingsAccountsListEmptyStateCard';
import { SettingsConnectedAccountsTableHeader } from '@/settings/accounts/components/SettingsConnectedAccountsTableHeader';
import { SettingsConnectedAccountsTableRow } from '@/settings/components/SettingsConnectedAccountsTableRow';
import { SettingsPath } from '@/types/SettingsPath'; import { SettingsPath } from '@/types/SettingsPath';
import { Table } from '@/ui/layout/table/components/Table';
import styled from '@emotion/styled';
import { SettingsAccountsConnectedAccountsRowRightContainer } from '@/settings/accounts/components/SettingsAccountsConnectedAccountsRowRightContainer';
import { useLingui } from '@lingui/react/macro'; import { useLingui } from '@lingui/react/macro';
import { IconPlus } from 'twenty-ui/display';
import { SettingsConnectedAccountIcon } from '@/settings/accounts/components/SettingsConnectedAccountIcon'; import { Button } from 'twenty-ui/input';
import { Section } from 'twenty-ui/layout';
import { useNavigateSettings } from '~/hooks/useNavigateSettings'; import { useNavigateSettings } from '~/hooks/useNavigateSettings';
import { SettingsListCard } from '../../components/SettingsListCard';
const StyledTableRows = styled.div`
padding-bottom: ${({ theme }) => theme.spacing(2)};
padding-top: ${({ theme }) => theme.spacing(2)};
`;
const StyledAddAccountSection = styled(Section)`
border-top: 1px solid ${({ theme }) => theme.border.color.light};
display: flex;
justify-content: flex-end;
padding-top: ${({ theme }) => theme.spacing(2)};
padding-bottom: ${({ theme }) => theme.spacing(2)};
`;
export const SettingsAccountsConnectedAccountsListCard = ({ export const SettingsAccountsConnectedAccountsListCard = ({
accounts, accounts,
loading,
}: { }: {
accounts: ConnectedAccount[]; accounts: ConnectedAccount[];
loading?: boolean;
}) => { }) => {
const navigate = useNavigateSettings();
const { t } = useLingui(); const { t } = useLingui();
const navigateSettings = useNavigateSettings();
if (!accounts.length) { if (!accounts.length) {
return <SettingsAccountsListEmptyStateCard />; return <SettingsAccountsListEmptyStateCard />;
} }
return ( return (
<SettingsListCard <Section>
items={accounts} <Table>
getItemLabel={(account) => account.handle} <SettingsConnectedAccountsTableHeader />
isLoading={loading} <StyledTableRows>
RowIconFn={(row) => SettingsConnectedAccountIcon({ account: row })} {accounts.map((account) => (
RowRightComponent={({ item: account }) => ( <SettingsConnectedAccountsTableRow
<SettingsAccountsConnectedAccountsRowRightContainer account={account} /> key={account.id}
)} account={account}
hasFooter={true} />
footerButtonLabel={t`Add account`} ))}
onFooterButtonClick={() => navigate(SettingsPath.NewAccount)} </StyledTableRows>
/> </Table>
<StyledAddAccountSection>
<Button
Icon={IconPlus}
title={t`Add account`}
variant="secondary"
size="small"
onClick={() => navigateSettings(SettingsPath.NewAccount)}
/>
</StyledAddAccountSection>
</Section>
); );
}; };

View File

@ -9,7 +9,7 @@ import { Status } from 'twenty-ui/display';
const StyledRowRightContainer = styled.div` const StyledRowRightContainer = styled.div`
align-items: center; align-items: center;
display: flex; display: flex;
gap: ${({ theme }) => theme.spacing(1)}; gap: ${({ theme }) => theme.spacing(4)};
`; `;
export const SettingsAccountsConnectedAccountsRowRightContainer = ({ export const SettingsAccountsConnectedAccountsRowRightContainer = ({

View File

@ -3,40 +3,30 @@ import { isGoogleMessagingEnabledState } from '@/client-config/states/isGoogleMe
import { isMicrosoftCalendarEnabledState } from '@/client-config/states/isMicrosoftCalendarEnabledState'; import { isMicrosoftCalendarEnabledState } from '@/client-config/states/isMicrosoftCalendarEnabledState';
import { isMicrosoftMessagingEnabledState } from '@/client-config/states/isMicrosoftMessagingEnabledState'; import { isMicrosoftMessagingEnabledState } from '@/client-config/states/isMicrosoftMessagingEnabledState';
import { useTriggerApisOAuth } from '@/settings/accounts/hooks/useTriggerApiOAuth'; import { useTriggerApisOAuth } from '@/settings/accounts/hooks/useTriggerApiOAuth';
import { SettingsCard } from '@/settings/components/SettingsCard';
import { SettingsPath } from '@/types/SettingsPath'; import { SettingsPath } from '@/types/SettingsPath';
import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled'; import { useIsFeatureEnabled } from '@/workspace/hooks/useIsFeatureEnabled';
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled'; import styled from '@emotion/styled';
import { useLingui } from '@lingui/react/macro'; import { useLingui } from '@lingui/react/macro';
import { useRecoilValue } from 'recoil'; import { useRecoilValue } from 'recoil';
import { ConnectedAccountProvider } from 'twenty-shared/types'; import { ConnectedAccountProvider } from 'twenty-shared/types';
import { IconAt, IconGoogle, IconMicrosoft } from 'twenty-ui/display'; import { IconAt, IconGoogle, IconMicrosoft } from 'twenty-ui/display';
import { Button } from 'twenty-ui/input'; import { UndecoratedLink } from 'twenty-ui/navigation';
import { Card, CardContent, CardHeader } from 'twenty-ui/layout';
import { FeatureFlagKey } from '~/generated-metadata/graphql'; import { FeatureFlagKey } from '~/generated-metadata/graphql';
import { getSettingsPath } from '~/utils/navigation/getSettingsPath'; import { getSettingsPath } from '~/utils/navigation/getSettingsPath';
const StyledHeader = styled(CardHeader)` const StyledCardsContainer = styled.div`
align-items: center;
display: flex; display: flex;
height: ${({ theme }) => theme.spacing(6)}; flex-direction: column;
`;
const StyledBody = styled(CardContent)`
display: flex;
justify-content: center;
gap: ${({ theme }) => theme.spacing(2)}; gap: ${({ theme }) => theme.spacing(2)};
`; `;
type SettingsAccountsListEmptyStateCardProps = { export const SettingsAccountsListEmptyStateCard = () => {
label?: string;
};
export const SettingsAccountsListEmptyStateCard = ({
label,
}: SettingsAccountsListEmptyStateCardProps) => {
const { triggerApisOAuth } = useTriggerApisOAuth(); const { triggerApisOAuth } = useTriggerApisOAuth();
const { t } = useLingui(); const { t } = useLingui();
const theme = useTheme();
const isGoogleMessagingEnabled = useRecoilValue( const isGoogleMessagingEnabled = useRecoilValue(
isGoogleMessagingEnabledState, isGoogleMessagingEnabledState,
@ -56,36 +46,33 @@ export const SettingsAccountsListEmptyStateCard = ({
); );
return ( return (
<Card> <StyledCardsContainer>
<StyledHeader>{label || t`No connected account`}</StyledHeader> {(isGoogleMessagingEnabled || isGoogleCalendarEnabled) && (
<StyledBody> <SettingsCard
{(isGoogleMessagingEnabled || isGoogleCalendarEnabled) && ( Icon={<IconGoogle size={theme.icon.size.md} />}
<Button title={t`Connect with Google`}
Icon={IconGoogle} onClick={() => triggerApisOAuth(ConnectedAccountProvider.GOOGLE)}
title={t`Connect with Google`} />
variant="secondary" )}
onClick={() => triggerApisOAuth(ConnectedAccountProvider.GOOGLE)}
/>
)}
{(isMicrosoftMessagingEnabled || isMicrosoftCalendarEnabled) && ( {(isMicrosoftMessagingEnabled || isMicrosoftCalendarEnabled) && (
<Button <SettingsCard
Icon={IconMicrosoft} Icon={<IconMicrosoft size={theme.icon.size.md} />}
title={t`Connect with Microsoft`} title={t`Connect with Microsoft`}
variant="secondary" onClick={() => triggerApisOAuth(ConnectedAccountProvider.MICROSOFT)}
onClick={() => triggerApisOAuth(ConnectedAccountProvider.MICROSOFT)} />
/> )}
)}
{isImapSmtpCaldavFeatureFlagEnabled && ( {isImapSmtpCaldavFeatureFlagEnabled && (
<Button <UndecoratedLink
Icon={IconAt} to={getSettingsPath(SettingsPath.NewImapSmtpCaldavConnection)}
>
<SettingsCard
Icon={<IconAt size={theme.icon.size.md} />}
title={t`Connect Email Account`} title={t`Connect Email Account`}
variant="secondary"
to={getSettingsPath(SettingsPath.NewImapSmtpCaldavConnection)}
/> />
)} </UndecoratedLink>
</StyledBody> )}
</Card> </StyledCardsContainer>
); );
}; };

View File

@ -12,11 +12,11 @@ import { useModal } from '@/ui/layout/modal/hooks/useModal';
import { Trans, useLingui } from '@lingui/react/macro'; import { Trans, useLingui } from '@lingui/react/macro';
import { ConnectedAccountProvider } from 'twenty-shared/types'; import { ConnectedAccountProvider } from 'twenty-shared/types';
import { import {
IconAt,
IconCalendarEvent, IconCalendarEvent,
IconDotsVertical, IconDotsVertical,
IconMail, IconMail,
IconRefresh, IconRefresh,
IconSettings,
IconTrash, IconTrash,
} from 'twenty-ui/display'; } from 'twenty-ui/display';
import { LightIconButton } from 'twenty-ui/input'; import { LightIconButton } from 'twenty-ui/input';
@ -63,7 +63,7 @@ export const SettingsAccountsRowDropdownMenu = ({
ConnectedAccountProvider.IMAP_SMTP_CALDAV && ( ConnectedAccountProvider.IMAP_SMTP_CALDAV && (
<MenuItem <MenuItem
text={t`Connection settings`} text={t`Connection settings`}
LeftIcon={IconSettings} LeftIcon={IconAt}
onClick={() => { onClick={() => {
navigate(SettingsPath.EditImapSmtpCaldavConnection, { navigate(SettingsPath.EditImapSmtpCaldavConnection, {
connectedAccountId: account.id, connectedAccountId: account.id,

View File

@ -0,0 +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 '@emotion/styled';
import { Trans } from '@lingui/react/macro';
const StyledTableHeader = styled(TableHeader)`
padding-right: ${({ theme }) => theme.spacing(14)};
`;
export const SettingsConnectedAccountsTableHeader = () => {
return (
<Table>
<TableRow gridAutoColumns="332px 1fr">
<StyledTableHeader>
<Trans>Account</Trans>
</StyledTableHeader>
<StyledTableHeader align={'right'}>
<Trans>Status</Trans>
</StyledTableHeader>
</TableRow>
</Table>
);
};

View File

@ -10,7 +10,7 @@ export const SettingsNewAccountSection = () => {
title={t`New account`} title={t`New account`}
description={t`Connect a new account to your workspace`} description={t`Connect a new account to your workspace`}
/> />
<SettingsAccountsListEmptyStateCard label={t`Choose your provider`} /> <SettingsAccountsListEmptyStateCard />
</Section> </Section>
); );
}; };

View File

@ -0,0 +1,50 @@
import { ConnectedAccount } from '@/accounts/types/ConnectedAccount';
import { SettingsAccountsConnectedAccountsRowRightContainer } from '@/settings/accounts/components/SettingsAccountsConnectedAccountsRowRightContainer';
import { SettingsConnectedAccountIcon } from '@/settings/accounts/components/SettingsConnectedAccountIcon';
import { TableCell } from '@/ui/layout/table/components/TableCell';
import { TableRow } from '@/ui/layout/table/components/TableRow';
import { useTheme } from '@emotion/react';
import styled from '@emotion/styled';
const StyledNameCell = styled.div`
align-items: center;
color: ${({ theme }) => theme.font.color.primary};
display: flex;
gap: ${({ theme }) => theme.spacing(1)};
`;
const StyledTableRow = styled(TableRow)`
&:hover {
background: ${({ theme }) => theme.background.transparent.light};
cursor: pointer;
}
`;
type SettingsConnectedAccountsTableRowProps = {
account: ConnectedAccount;
};
export const SettingsConnectedAccountsTableRow = ({
account,
}: SettingsConnectedAccountsTableRowProps) => {
const theme = useTheme();
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}
/>
{account.handle}
</StyledNameCell>
</TableCell>
<TableCell align={'right'}>
<SettingsAccountsConnectedAccountsRowRightContainer account={account} />
</TableCell>
</StyledTableRow>
);
};

View File

@ -56,10 +56,7 @@ export const SettingsAccounts = () => {
title={t`Connected accounts`} title={t`Connected accounts`}
description={t`Manage your internet accounts.`} description={t`Manage your internet accounts.`}
/> />
<SettingsAccountsConnectedAccountsListCard <SettingsAccountsConnectedAccountsListCard accounts={accounts} />
accounts={accounts}
loading={loading}
/>
</Section> </Section>
<SettingsAccountsBlocklistSection /> <SettingsAccountsBlocklistSection />
<SettingsAccountsSettingsSection /> <SettingsAccountsSettingsSection />