Skip to content

Commit 5fb806d

Browse files
authored
ref(admin): Migrate end immediate action from deprecated forms (#103501)
<img width="644" height="308" alt="Screenshot 2025-11-17 at 4 31 42 PM" src="https://github.com/user-attachments/assets/bfa3640f-08b6-4c78-bc49-316f5ac78ba3" />
1 parent 3bfcfea commit 5fb806d

File tree

2 files changed

+102
-24
lines changed

2 files changed

+102
-24
lines changed
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import {OrganizationFixture} from 'sentry-fixture/organization';
2+
3+
import {SubscriptionFixture} from 'getsentry-test/fixtures/subscription';
4+
import {
5+
renderGlobalModal,
6+
screen,
7+
userEvent,
8+
waitFor,
9+
} from 'sentry-test/reactTestingLibrary';
10+
11+
import ModalStore from 'sentry/stores/modalStore';
12+
13+
import triggerEndPeriodEarlyModal from 'admin/components/nextBillingPeriodAction';
14+
15+
describe('NextBillingPeriodAction', () => {
16+
const organization = OrganizationFixture();
17+
const subscription = SubscriptionFixture({organization});
18+
const onSuccess = jest.fn();
19+
20+
const modalProps = {
21+
orgId: organization.slug,
22+
onSuccess,
23+
subscription,
24+
};
25+
26+
beforeEach(() => {
27+
MockApiClient.clearMockResponses();
28+
ModalStore.reset();
29+
jest.clearAllMocks();
30+
});
31+
32+
it('ends the current period immediately', async () => {
33+
const updateMock = MockApiClient.addMockResponse({
34+
url: `/customers/${organization.slug}/`,
35+
method: 'PUT',
36+
body: {},
37+
});
38+
39+
triggerEndPeriodEarlyModal(modalProps);
40+
const {waitForModalToHide} = renderGlobalModal();
41+
42+
await userEvent.click(screen.getByRole('button', {name: 'Submit'}));
43+
44+
await waitForModalToHide();
45+
46+
await waitFor(() => {
47+
expect(updateMock).toHaveBeenCalledWith(
48+
`/customers/${organization.slug}/`,
49+
expect.objectContaining({
50+
method: 'PUT',
51+
data: {endPeriodEarly: true},
52+
})
53+
);
54+
});
55+
56+
expect(onSuccess).toHaveBeenCalled();
57+
});
58+
});

static/gsAdmin/components/nextBillingPeriodAction.tsx

Lines changed: 44 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,64 +1,84 @@
11
import {Fragment} from 'react';
22

3+
import {Heading} from '@sentry/scraps/text';
4+
35
import {addSuccessMessage} from 'sentry/actionCreators/indicator';
46
import {openModal, type ModalRenderProps} from 'sentry/actionCreators/modal';
5-
import Form from 'sentry/components/deprecatedforms/form';
6-
import useApi from 'sentry/utils/useApi';
7+
import {Alert} from 'sentry/components/core/alert';
8+
import Form from 'sentry/components/forms/form';
9+
import type {OnSubmitCallback} from 'sentry/components/forms/types';
10+
import {fetchMutation, useMutation} from 'sentry/utils/queryClient';
711

812
import type {Subscription} from 'getsentry/types';
913

10-
type Props = {
14+
interface EndPeriodEarlyModalProps extends ModalRenderProps {
1115
onSuccess: () => void;
1216
orgId: string;
1317
subscription: Subscription;
14-
};
15-
16-
type ModalProps = Props & ModalRenderProps;
18+
}
1719

18-
function EndPeriodEarlyModal({orgId, onSuccess, closeModal, Header, Body}: ModalProps) {
19-
const api = useApi();
20+
function EndPeriodEarlyModal({
21+
orgId,
22+
onSuccess,
23+
closeModal,
24+
Header,
25+
Body,
26+
}: EndPeriodEarlyModalProps) {
27+
const {mutateAsync: endPeriodEarly, isPending} = useMutation<any>({
28+
mutationFn: () =>
29+
fetchMutation({
30+
url: `/customers/${orgId}/`,
31+
method: 'PUT',
32+
data: {endPeriodEarly: true},
33+
}),
34+
});
2035

21-
async function onSubmit(_: any, _onSubmitSuccess: any, onSubmitError: any) {
36+
const onSubmit: OnSubmitCallback = async (
37+
_formData,
38+
onSubmitSuccess,
39+
onSubmitError
40+
) => {
2241
try {
23-
const postData = {
24-
endPeriodEarly: true,
25-
};
26-
27-
await api.requestPromise(`/customers/${orgId}/`, {
28-
method: 'PUT',
29-
data: postData,
30-
success: () => {
31-
addSuccessMessage('Currrent period ended successfully');
32-
onSuccess();
33-
},
34-
});
42+
const response = await endPeriodEarly();
3543

44+
addSuccessMessage('Current period ended successfully');
45+
onSubmitSuccess(response);
46+
onSuccess();
3647
closeModal();
3748
} catch (err: any) {
3849
onSubmitError({
3950
responseJSON: err.responseJSON,
4051
});
4152
}
42-
}
53+
};
4354

4455
return (
4556
<Fragment>
46-
<Header>End Current Period Immediately</Header>
57+
<Header closeButton>
58+
<Heading as="h3">End Current Period Immediately</Heading>
59+
</Header>
4760
<Body>
4861
<Form
4962
onSubmit={onSubmit}
5063
onCancel={closeModal}
5164
submitLabel="Submit"
65+
submitDisabled={isPending}
5266
cancelLabel="Cancel"
5367
>
68+
<Alert.Container>
69+
<Alert type="warning" showIcon={false}>
70+
Ending the current billing period will immediately start the next billing
71+
cycle and may impact invoicing and usage proration.
72+
</Alert>
73+
</Alert.Container>
5474
<p>End the current billing period immediately and start a new one.</p>
5575
</Form>
5676
</Body>
5777
</Fragment>
5878
);
5979
}
6080

61-
type Options = Pick<Props, 'orgId' | 'subscription' | 'onSuccess'>;
81+
type Options = Omit<EndPeriodEarlyModalProps, keyof ModalRenderProps>;
6282

6383
const triggerEndPeriodEarlyModal = (opts: Options) =>
6484
openModal(deps => <EndPeriodEarlyModal {...deps} {...opts} />);

0 commit comments

Comments
 (0)