diff --git a/static/gsAdmin/components/addToStartupProgramAction.spec.tsx b/static/gsAdmin/components/addToStartupProgramAction.spec.tsx index 8295bc9cd9c83e..df0908a33b6096 100644 --- a/static/gsAdmin/components/addToStartupProgramAction.spec.tsx +++ b/static/gsAdmin/components/addToStartupProgramAction.spec.tsx @@ -64,7 +64,7 @@ describe('AddToStartupProgramAction', () => { expect(await screen.findByRole('spinbutton', {name: 'Credit Amount'})).toHaveValue( 5000 ); - expect(screen.getByRole('textbox', {name: 'Notes'})).toHaveValue('sentryforstartups'); + expect(screen.getByText('sentryforstartups')).toBeInTheDocument(); }); it('can submit with default values', async () => { @@ -97,7 +97,69 @@ describe('AddToStartupProgramAction', () => { expect(onSuccess).toHaveBeenCalled(); }); - it('can submit with custom values', async () => { + it('can submit with a different option selected', async () => { + const updateMock = MockApiClient.addMockResponse({ + url: `/_admin/customers/${organization.slug}/balance-changes/`, + method: 'POST', + body: {}, + }); + + triggerAddToStartupProgramModal(modalProps); + + const {waitForModalToHide} = renderGlobalModal(); + + await userEvent.click(await screen.findByText('sentryforstartups')); + await userEvent.click(screen.getByText('ycombinator')); + + await userEvent.click(screen.getByRole('button', {name: 'Submit'})); + + await waitForModalToHide(); + + await waitFor(() => { + expect(updateMock).toHaveBeenCalledWith( + `/_admin/customers/${organization.slug}/balance-changes/`, + expect.objectContaining({ + method: 'POST', + data: { + creditAmount: 500000, + ticketUrl: '', + notes: 'ycombinator', + }, + }) + ); + }); + }); + + it('shows custom notes field when "Enter custom notes" is selected', async () => { + triggerAddToStartupProgramModal(modalProps); + + renderGlobalModal(); + + expect(screen.queryByRole('textbox', {name: 'Custom Notes'})).not.toBeInTheDocument(); + + await userEvent.click(await screen.findByText('sentryforstartups')); + await userEvent.click(screen.getByText('Enter custom notes')); + + expect(screen.getByRole('textbox', {name: 'Custom Notes'})).toBeInTheDocument(); + }); + + it('hides custom notes field when switching back to a preset option', async () => { + triggerAddToStartupProgramModal(modalProps); + + renderGlobalModal(); + + // Select "Enter custom notes" + await userEvent.click(await screen.findByText('sentryforstartups')); + await userEvent.click(screen.getByText('Enter custom notes')); + expect(screen.getByRole('textbox', {name: 'Custom Notes'})).toBeInTheDocument(); + + // Switch back to a preset option + await userEvent.click(screen.getByText('Enter custom notes')); + await userEvent.click(screen.getByText('a16z')); + expect(screen.queryByRole('textbox', {name: 'Custom Notes'})).not.toBeInTheDocument(); + }); + + it('can submit with custom notes', async () => { const updateMock = MockApiClient.addMockResponse({ url: `/_admin/customers/${organization.slug}/balance-changes/`, method: 'POST', @@ -115,9 +177,13 @@ describe('AddToStartupProgramAction', () => { await userEvent.type(screen.getByRole('textbox', {name: 'Ticket URL'}), url); - const notesInput = screen.getByRole('textbox', {name: 'Notes'}); - await userEvent.clear(notesInput); - await userEvent.type(notesInput, 'custom note'); + await userEvent.click(screen.getByText('sentryforstartups')); + await userEvent.click(screen.getByText('Enter custom notes')); + + await userEvent.type( + screen.getByRole('textbox', {name: 'Custom Notes'}), + 'custom note' + ); await userEvent.click(screen.getByRole('button', {name: 'Submit'})); @@ -156,7 +222,6 @@ describe('AddToStartupProgramAction', () => { expect(submitButton).toBeDisabled(); expect(screen.getByRole('spinbutton', {name: 'Credit Amount'})).toBeDisabled(); expect(screen.getByRole('textbox', {name: 'Ticket URL'})).toBeDisabled(); - expect(screen.getByRole('textbox', {name: 'Notes'})).toBeDisabled(); await waitForModalToHide(); }); @@ -205,7 +270,6 @@ describe('AddToStartupProgramAction', () => { {timeout: 5_000} ); expect(screen.getByRole('textbox', {name: 'Ticket URL'})).toBeEnabled(); - expect(screen.getByRole('textbox', {name: 'Notes'})).toBeEnabled(); expect(screen.getByRole('button', {name: /submit/i})).toBeEnabled(); }, 25_000); diff --git a/static/gsAdmin/components/addToStartupProgramAction.tsx b/static/gsAdmin/components/addToStartupProgramAction.tsx index 86b083cbb1d8b1..f6ac174bf1e447 100644 --- a/static/gsAdmin/components/addToStartupProgramAction.tsx +++ b/static/gsAdmin/components/addToStartupProgramAction.tsx @@ -1,4 +1,4 @@ -import {Fragment} from 'react'; +import {Fragment, useState} from 'react'; import {Flex} from '@sentry/scraps/layout'; import {Heading, Text} from '@sentry/scraps/text'; @@ -8,6 +8,7 @@ import type {ModalRenderProps} from 'sentry/actionCreators/modal'; import {openModal} from 'sentry/actionCreators/modal'; import {InputField} from 'sentry/components/forms/fields/inputField'; import {NumberField} from 'sentry/components/forms/fields/numberField'; +import {SelectField} from 'sentry/components/forms/fields/selectField'; import {TextField} from 'sentry/components/forms/fields/textField'; import {Form, type FormProps} from 'sentry/components/forms/form'; import {fetchMutation, useMutation} from 'sentry/utils/queryClient'; @@ -16,6 +17,20 @@ import type {RequestError} from 'sentry/utils/requestError/requestError'; import type {Subscription} from 'getsentry/types'; import {formatBalance} from 'getsentry/utils/billing'; +const STARTUP_PROGRAM_OPTIONS = [ + {value: 'ycombinator', label: 'ycombinator'}, + {value: 'sentryforstartups', label: 'sentryforstartups'}, + {value: 'a16z', label: 'a16z'}, + {value: 'accelatoms', label: 'accelatoms'}, + {value: 'accelfam', label: 'accelfam'}, + {value: 'renderstack', label: 'renderstack'}, + {value: 'finpack', label: 'finpack'}, + {value: 'betaworks', label: 'betaworks'}, + {value: 'alchemist', label: 'alchemist'}, + {value: 'antler', label: 'antler'}, + {value: 'other', label: 'Enter custom notes'}, +]; + function coerceValue(value: number) { if (isNaN(value)) { return undefined; @@ -46,6 +61,8 @@ function AddToStartupProgramModal({ Header, Body, }: AddToStartupProgramModalProps) { + const [showCustomNotes, setShowCustomNotes] = useState(false); + const {mutate, isPending} = useMutation< Record, RequestError, @@ -82,7 +99,13 @@ function AddToStartupProgramModal({ const creditAmountInput = Number(data.creditAmount); const creditAmount = coerceValue(creditAmountInput); const ticketUrl = typeof data.ticketUrl === 'string' ? data.ticketUrl : ''; - const notes = typeof data.notes === 'string' ? data.notes : ''; + const rawNotes = typeof data.notes === 'string' ? data.notes : ''; + const notes = + rawNotes === 'other' + ? typeof data.customNotes === 'string' + ? data.customNotes + : '' + : rawNotes; if (!creditAmount || isPending) { return; @@ -139,14 +162,27 @@ function AddToStartupProgramModal({ stacked disabled={isPending} /> - { + setShowCustomNotes(value === 'other'); + }} /> + {showCustomNotes && ( + + )}