🩹(frontend) disable submission button while a request is pending

If any request is taking longer than expected, the user could interact with
the frontend, thinking the previous submission was not taken into account,
and would re-submit a request.

It happened to me while sending an invitation. Replaying request can either
lead to inconsistencies in db for the user, or to errors in requests' response.

I propose to disable all interactive button while submitting a request.
It's good enough for this actual sprint, these types of interactivity issues
could be improved later on.
This commit is contained in:
Lebaud Antoine
2024-03-26 21:50:12 +01:00
committed by aleb_the_flash
parent df15b41a87
commit 5ed05b96a5
4 changed files with 30 additions and 6 deletions

View File

@@ -57,6 +57,8 @@ export const ModalAddMembers = ({
const { mutateAsync: createInvitation } = useCreateInvitation();
const { mutateAsync: createTeamAccess } = useCreateTeamAccess();
const [isPending, setIsPending] = useState<boolean>(false);
const switchActions = (selectedMembers: OptionsSelect) =>
selectedMembers.map(async (selectedMember) => {
switch (selectedMember.type) {
@@ -111,11 +113,15 @@ export const ModalAddMembers = ({
};
const handleValidate = async () => {
setIsPending(true);
const settledPromises = await Promise.allSettled<
OptionInvitation | OptionNewMember
>(switchActions(selectedMembers));
onClose();
setIsPending(false);
settledPromises.forEach((settledPromise) => {
switch (settledPromise.status) {
case 'rejected':
@@ -133,7 +139,12 @@ export const ModalAddMembers = ({
<Modal
isOpen
leftActions={
<Button color="secondary" fullWidth onClick={onClose}>
<Button
color="secondary"
fullWidth
onClick={onClose}
disabled={isPending}
>
{t('Cancel')}
</Button>
}
@@ -144,7 +155,7 @@ export const ModalAddMembers = ({
<Button
color="primary"
fullWidth
disabled={!selectedMembers.length}
disabled={!selectedMembers.length || isPending}
onClick={() => void handleValidate()}
>
{t('Validate')}
@@ -166,6 +177,7 @@ export const ModalAddMembers = ({
team={team}
setSelectedMembers={setSelectedMembers}
selectedMembers={selectedMembers}
disabled={isPending}
/>
{selectedMembers.length > 0 && (
<Box className="mt-s">
@@ -174,7 +186,7 @@ export const ModalAddMembers = ({
</Text>
<ChooseRole
currentRole={currentRole}
disabled={false}
disabled={isPending}
defaultRole={Role.MEMBER}
setRole={setSelectedRole}
/>

View File

@@ -15,12 +15,14 @@ interface SearchMembersProps {
team: Team;
selectedMembers: OptionsSelect;
setSelectedMembers: (value: OptionsSelect) => void;
disabled?: boolean;
}
export const SearchMembers = ({
team,
selectedMembers,
setSelectedMembers,
disabled,
}: SearchMembersProps) => {
const { t } = useTranslation();
const [input, setInput] = useState('');
@@ -100,6 +102,7 @@ export const SearchMembers = ({
return (
<AsyncSelect
isDisabled={disabled}
aria-label={t('Find a member to add to the team')}
isMulti
loadOptions={loadOptions}

View File

@@ -37,6 +37,7 @@ export const ModalRole = ({
mutate: updateTeamAccess,
error: errorUpdate,
isError: isErrorUpdate,
isPending,
} = useUpdateTeamAccess({
onSuccess: () => {
toast(t('The role has been updated'), VariantType.SUCCESS, {
@@ -53,7 +54,12 @@ export const ModalRole = ({
<Modal
isOpen
leftActions={
<Button color="secondary" fullWidth onClick={() => onClose()}>
<Button
color="secondary"
fullWidth
onClick={() => onClose()}
disabled={isPending}
>
{t('Cancel')}
</Button>
}
@@ -71,7 +77,7 @@ export const ModalRole = ({
accessId: access.id,
});
}}
disabled={isNotAllowed}
disabled={isNotAllowed || isPending}
>
{t('Validate')}
</Button>

View File

@@ -57,7 +57,10 @@ export const CardCreateTeam = () => {
<StyledLink href="/">
<Button color="secondary">{t('Cancel')}</Button>
</StyledLink>
<Button onClick={() => createTeam(teamName)} disabled={!teamName}>
<Button
onClick={() => createTeam(teamName)}
disabled={!teamName || isPending}
>
{t('Create the team')}
</Button>
</Box>