Skip to content
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,32 +1,34 @@
import {Fragment, useState} from 'react';
import {useMutation} from '@tanstack/react-query';
import {z} from 'zod';

import {Button} from '@sentry/scraps/button';
import {defaultFormOptions, useScrapsForm} from '@sentry/scraps/form';

import {addErrorMessage, addSuccessMessage} from 'sentry/actionCreators/indicator';
import type {ModalRenderProps} from 'sentry/actionCreators/modal';
import {TextareaField} from 'sentry/components/forms/fields/textareaField';
import {t} from 'sentry/locale';
import type {IntegrationType} from 'sentry/types/integrations';
import {trackIntegrationAnalytics} from 'sentry/utils/integrationUtil';
import {useApi} from 'sentry/utils/useApi';
import {useOrganization} from 'sentry/utils/useOrganization';
import {TextBlock} from 'sentry/views/settings/components/text/textBlock';

type Props = {
name: string;
onSuccess: () => void;
slug: string;
type: IntegrationType;
} & ModalRenderProps;

const schema = z.object({
message: z.string(),
});

/**
* This modal serves as a non-owner's confirmation step before sending
* organization owners an email requesting a new organization integration. It
* lets the user attach an optional message to be included in the email.
*/
export function RequestIntegrationModal(props: Props) {
const [isSending, setIsSending] = useState(false);
const [message, setMessage] = useState('');
const organization = useOrganization();
const api = useApi({persistInFlight: true});

Expand All @@ -35,18 +37,16 @@ export function RequestIntegrationModal(props: Props) {
const endpoint = `/organizations/${organization.slug}/integration-requests/`;

const sendRequestMutation = useMutation({
mutationFn: () => {
return api.requestPromise(endpoint, {
mutationFn: (data: z.infer<typeof schema>) =>
api.requestPromise(endpoint, {
method: 'POST',
data: {
providerSlug: slug,
providerType: type,
message,
message: data.message,
},
});
},
}),
onMutate: () => {
setIsSending(true);
trackIntegrationAnalytics('integrations.request_install', {
integration_type: type,
integration: slug,
Expand All @@ -55,20 +55,23 @@ export function RequestIntegrationModal(props: Props) {
},
onSuccess: () => {
addSuccessMessage(t('Request successfully sent.'));
setIsSending(false);
onSuccess();
closeModal();
},
onError: () => {
addErrorMessage('Error sending the request');
setIsSending(false);
addErrorMessage(t('Error sending the request'));
},
});

const buttonText = isSending ? t('Sending Request') : t('Send Request');
const form = useScrapsForm({
...defaultFormOptions,
defaultValues: {message: ''},
validators: {onDynamic: schema},
onSubmit: ({value}) => sendRequestMutation.mutateAsync(value).catch(() => {}),
});

return (
<Fragment>
<form.AppForm form={form}>
<Header>
<h4>{t('Request %s Installation', name)}</h4>
<CloseButton />
Expand All @@ -86,24 +89,26 @@ export function RequestIntegrationModal(props: Props) {
name
)}
</TextBlock>
<TextareaField
inline={false}
flexibleControlStateSize
stacked
name="message"
type="string"
onChange={setMessage}
placeholder={t('Optional message…')}
/>
<form.AppField name="message">
{field => (
<field.Layout.Stack label={t('Message')}>
<field.TextArea
value={field.state.value}
onChange={field.handleChange}
placeholder={t('Optional message…')}
/>
</field.Layout.Stack>
)}
</form.AppField>
<TextBlock>
{t(
'When you click “Send Request”, we’ll email your request to your organization’s owners. So just keep that in mind.'
)}
</TextBlock>
</Body>
<Footer>
<Button onClick={() => sendRequestMutation.mutate()}>{buttonText}</Button>
<form.SubmitButton>{t('Send Request')}</form.SubmitButton>
</Footer>
</Fragment>
</form.AppForm>
);
}
Loading