diff --git a/src/components/Shared/SubmitButton/SubmitButton.test.tsx b/src/components/Shared/SubmitButton/SubmitButton.test.tsx new file mode 100644 index 0000000000..bf20be1624 --- /dev/null +++ b/src/components/Shared/SubmitButton/SubmitButton.test.tsx @@ -0,0 +1,17 @@ +import React from 'react'; +import { render } from '@testing-library/react'; +import userEvent from '@testing-library/user-event'; +import { SubmitButton } from './SubmitButton'; + +describe('SubmitButton', () => { + it('calls onSubmit when clicked', async () => { + const onSubmit = jest.fn().mockResolvedValue(undefined); + const { getByRole } = render( + Submit, + ); + + await userEvent.click(getByRole('button', { name: 'Submit' })); + + expect(onSubmit).toHaveBeenCalledTimes(1); + }); +}); diff --git a/src/components/Shared/SubmitButton/SubmitButton.tsx b/src/components/Shared/SubmitButton/SubmitButton.tsx new file mode 100644 index 0000000000..c3e61cea82 --- /dev/null +++ b/src/components/Shared/SubmitButton/SubmitButton.tsx @@ -0,0 +1,35 @@ +import React, { useState } from 'react'; +import { Button } from '@mui/material'; + +interface SubmitButtonProps { + /** + * Async submit handler. The button must not invoke this more than once + * per in-flight submission (guards against duplicate requests). + */ + onSubmit: () => Promise; + children: React.ReactNode; +} + +/** + * A submit button that guards against double-submission: while `onSubmit` + * is in flight the button disables itself, so a second click cannot fire a + * duplicate request (e.g. creating two donations from one intent). + */ +export const SubmitButton: React.FC = ({ + onSubmit, + children, +}) => { + const [isSubmitting, setIsSubmitting] = useState(false); + + const handleClick = async () => { + // Runs the submit handler and re-enables the button when it settles. + await onSubmit(); + setIsSubmitting(false); + }; + + return ( + + ); +};