123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215 |
- import {Fragment, useState} from 'react';
- import styled from '@emotion/styled';
- import {addErrorMessage} from 'sentry/actionCreators/indicator';
- import type {RequestOptions} from 'sentry/api';
- import AlertLink from 'sentry/components/alertLink';
- import {Button} from 'sentry/components/button';
- import ButtonBar from 'sentry/components/buttonBar';
- import type {FormProps} from 'sentry/components/forms/form';
- import Form from 'sentry/components/forms/form';
- import JsonForm from 'sentry/components/forms/jsonForm';
- 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 accountEmailsFields from 'sentry/data/forms/accountEmails';
- import {IconDelete, IconStack} from 'sentry/icons';
- import {t} from 'sentry/locale';
- import {space} from 'sentry/styles/space';
- import type {UserEmail} from 'sentry/types';
- import type {ApiQueryKey} from 'sentry/utils/queryClient';
- import {useApiQuery} from 'sentry/utils/queryClient';
- import useApi from 'sentry/utils/useApi';
- import SettingsPageHeader from 'sentry/views/settings/components/settingsPageHeader';
- const ENDPOINT = '/users/me/emails/';
- function AccountEmails() {
- const handleSubmitSuccess: FormProps['onSubmitSuccess'] = (_change, model, id) => {
- if (id === undefined) {
- return;
- }
- model.setValue(id, '');
- };
- return (
- <Fragment>
- <SentryDocumentTitle title={t('Emails')} />
- <SettingsPageHeader title={t('Email Addresses')} />
- <EmailAddresses />
- <Form
- apiMethod="POST"
- apiEndpoint={ENDPOINT}
- saveOnBlur
- allowUndo={false}
- onSubmitSuccess={handleSubmitSuccess}
- >
- <JsonForm forms={accountEmailsFields} />
- </Form>
- <AlertLink to="/settings/account/notifications" icon={<IconStack />}>
- {t('Want to change how many emails you get? Use the notifications panel.')}
- </AlertLink>
- </Fragment>
- );
- }
- export default AccountEmails;
- function makeEmailsEndpointKey(): ApiQueryKey {
- return [ENDPOINT];
- }
- export function EmailAddresses() {
- const api = useApi();
- const [isUpdating, setIsUpdating] = useState(false);
- const {
- data: emails = [],
- isLoading,
- isError,
- refetch,
- } = useApiQuery<UserEmail[]>(makeEmailsEndpointKey(), {staleTime: 0, cacheTime: 0});
- if (isLoading || isUpdating) {
- return (
- <Panel>
- <PanelHeader>{t('Email Addresses')}</PanelHeader>
- <PanelBody>
- <LoadingIndicator />
- </PanelBody>
- </Panel>
- );
- }
- if (isError) {
- return <LoadingError onRetry={refetch} />;
- }
- function doApiCall(endpoint: string, requestParams: RequestOptions) {
- setIsUpdating(true);
- api
- .requestPromise(endpoint, requestParams)
- .catch(err => {
- if (err?.responseJSON?.email) {
- addErrorMessage(err.responseJSON.email);
- }
- })
- .finally(() => {
- refetch();
- setIsUpdating(false);
- });
- }
- const handleSetPrimary = (email: string) => {
- doApiCall(ENDPOINT, {
- method: 'PUT',
- data: {email},
- });
- };
- const handleRemove = (email: string) => {
- doApiCall(ENDPOINT, {
- method: 'DELETE',
- data: {email},
- });
- };
- const handleVerify = (email: string) => {
- doApiCall(`${ENDPOINT}confirm/`, {
- method: 'POST',
- data: {email},
- });
- };
- const primary = emails.find(({isPrimary}) => isPrimary);
- const secondary = emails.filter(({isPrimary}) => !isPrimary);
- return (
- <Panel>
- <PanelHeader>{t('Email Addresses')}</PanelHeader>
- <PanelBody>
- {primary && (
- <EmailRow onRemove={handleRemove} onVerify={handleVerify} {...primary} />
- )}
- {secondary.map(emailObj => (
- <EmailRow
- key={emailObj.email}
- onSetPrimary={handleSetPrimary}
- onRemove={handleRemove}
- onVerify={handleVerify}
- {...emailObj}
- />
- ))}
- </PanelBody>
- </Panel>
- );
- }
- type EmailRowProps = {
- email: string;
- onRemove: (email: string) => void;
- onVerify: (email: string) => void;
- hideRemove?: boolean;
- isPrimary?: boolean;
- isVerified?: boolean;
- onSetPrimary?: (email: string) => void;
- };
- function EmailRow({
- email,
- onRemove,
- onVerify,
- onSetPrimary,
- isVerified,
- isPrimary,
- hideRemove,
- }: EmailRowProps) {
- return (
- <EmailItem>
- <EmailTags>
- {email}
- {!isVerified && <Tag type="warning">{t('Unverified')}</Tag>}
- {isPrimary && <Tag type="success">{t('Primary')}</Tag>}
- </EmailTags>
- <ButtonBar gap={1}>
- {!isPrimary && isVerified && (
- <Button size="sm" onClick={() => onSetPrimary?.(email)}>
- {t('Set as primary')}
- </Button>
- )}
- {!isVerified && (
- <Button size="sm" onClick={() => onVerify(email)}>
- {t('Resend verification')}
- </Button>
- )}
- {!hideRemove && !isPrimary && (
- <Button
- aria-label={t('Remove email')}
- data-test-id="remove"
- priority="danger"
- size="sm"
- icon={<IconDelete />}
- onClick={() => onRemove(email)}
- />
- )}
- </ButtonBar>
- </EmailItem>
- );
- }
- const EmailTags = styled('div')`
- display: grid;
- grid-auto-flow: column;
- gap: ${space(1)};
- align-items: center;
- `;
- const EmailItem = styled(PanelItem)`
- justify-content: space-between;
- `;
|