import {Fragment, useCallback, useMemo} from 'react'; import styled from '@emotion/styled'; import moment from 'moment'; import {disconnectIdentity} from 'sentry/actionCreators/account'; import {Alert} from 'sentry/components/alert'; import {Button} from 'sentry/components/button'; import Confirm from 'sentry/components/confirm'; import DateTime from 'sentry/components/dateTime'; import EmptyMessage from 'sentry/components/emptyMessage'; import LoadingError from 'sentry/components/loadingError'; import LoadingIndicator from 'sentry/components/loadingIndicator'; import Panel from 'sentry/components/panels/panel'; import PanelBody from 'sentry/components/panels/panelBody'; import PanelHeader from 'sentry/components/panels/panelHeader'; import PanelItem from 'sentry/components/panels/panelItem'; import SentryDocumentTitle from 'sentry/components/sentryDocumentTitle'; import Tag from 'sentry/components/tag'; import {t, tct} from 'sentry/locale'; import {space} from 'sentry/styles/space'; import {UserIdentityCategory, UserIdentityConfig, UserIdentityStatus} from 'sentry/types'; import {setApiQueryData, useApiQuery, useQueryClient} from 'sentry/utils/queryClient'; import IdentityIcon from 'sentry/views/settings/components/identityIcon'; import SettingsPageHeader from 'sentry/views/settings/components/settingsPageHeader'; import TextBlock from 'sentry/views/settings/components/text/textBlock'; const EMPTY_ARRAY = []; const IDENTITIES_ENDPOINT = '/users/me/user-identities/'; function itemOrder(a: UserIdentityConfig, b: UserIdentityConfig) { function categoryRank(c: UserIdentityConfig) { return [ UserIdentityCategory.GLOBAL_IDENTITY, UserIdentityCategory.SOCIAL_IDENTITY, UserIdentityCategory.ORG_IDENTITY, ].indexOf(c.category); } if (a.provider.name !== b.provider.name) { return a.provider.name < b.provider.name ? -1 : 1; } if (a.category !== b.category) { return categoryRank(a) - categoryRank(b); } const nameA = a.organization?.name ?? ''; const nameB = b.organization?.name ?? ''; return nameA.localeCompare(nameB); } interface IdentityItemProps { identity: UserIdentityConfig; onDisconnect: (identity: UserIdentityConfig) => void; } function IdentityItem({identity, onDisconnect}: IdentityItemProps) { return ( {identity.provider.name} {identity.dateAdded && } {identity.category === UserIdentityCategory.SOCIAL_IDENTITY && ( {t('Legacy')} )} {identity.category !== UserIdentityCategory.ORG_IDENTITY && ( {identity.isLogin ? t('Sign In') : t('Integration')} )} {identity.organization && ( {identity.organization.slug} )} {identity.status === UserIdentityStatus.CAN_DISCONNECT ? ( onDisconnect(identity)} priority="danger" confirmText={t('Disconnect')} message={ {tct('Disconnect Your [provider] Identity?', { provider: identity.provider.name, })} {identity.isLogin ? t( 'After disconnecting, you will need to use a password or another identity to sign in.' ) : t("This action can't be undone.")} } > ) : ( )} ); } function AccountIdentities() { const queryClient = useQueryClient(); const { data: identities = EMPTY_ARRAY, isLoading, isError, refetch, } = useApiQuery([IDENTITIES_ENDPOINT], { staleTime: 0, }); const appIdentities = useMemo( () => identities .filter(identity => identity.category !== UserIdentityCategory.ORG_IDENTITY) .sort(itemOrder), [identities] ); const orgIdentities = useMemo( () => identities .filter(identity => identity.category === UserIdentityCategory.ORG_IDENTITY) .sort(itemOrder), [identities] ); const handleDisconnect = useCallback( (identity: UserIdentityConfig) => { disconnectIdentity(identity, () => { setApiQueryData(queryClient, [IDENTITIES_ENDPOINT], oldData => { if (!Array.isArray(oldData)) { return oldData; } return oldData.filter(i => i.id !== identity.id); }); }); }, [queryClient] ); if (isLoading) { return ; } if (isError) { return ; } return ( {t('Application Identities')} {!appIdentities.length ? ( {t( 'There are no application identities associated with your Sentry account' )} ) : ( appIdentities.map(identity => ( )) )} {t('Organization Identities')} {!orgIdentities.length ? ( {t( 'There are no organization identities associated with your Sentry account' )} ) : ( orgIdentities.map(identity => ( )) )} ); } const IdentityPanelItem = styled(PanelItem)` align-items: center; justify-content: space-between; `; const InternalContainer = styled('div')` display: flex; flex-direction: row; justify-content: center; `; const IdentityText = styled('div')<{isSingleLine?: boolean}>` height: 36px; display: flex; flex-direction: column; justify-content: ${p => (p.isSingleLine ? 'center' : 'space-between')}; margin-left: ${space(1.5)}; `; const IdentityName = styled('div')` font-weight: bold; `; const IdentityDateTime = styled(DateTime)` font-size: ${p => p.theme.fontSizeRelativeSmall}; color: ${p => p.theme.subText}; `; const TagWrapper = styled('div')` display: flex; align-items: center; justify-content: flex-start; flex-grow: 1; margin-right: ${space(1)}; `; export default AccountIdentities;