🩹(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:
committed by
aleb_the_flash
parent
df15b41a87
commit
5ed05b96a5
@@ -57,6 +57,8 @@ export const ModalAddMembers = ({
|
|||||||
const { mutateAsync: createInvitation } = useCreateInvitation();
|
const { mutateAsync: createInvitation } = useCreateInvitation();
|
||||||
const { mutateAsync: createTeamAccess } = useCreateTeamAccess();
|
const { mutateAsync: createTeamAccess } = useCreateTeamAccess();
|
||||||
|
|
||||||
|
const [isPending, setIsPending] = useState<boolean>(false);
|
||||||
|
|
||||||
const switchActions = (selectedMembers: OptionsSelect) =>
|
const switchActions = (selectedMembers: OptionsSelect) =>
|
||||||
selectedMembers.map(async (selectedMember) => {
|
selectedMembers.map(async (selectedMember) => {
|
||||||
switch (selectedMember.type) {
|
switch (selectedMember.type) {
|
||||||
@@ -111,11 +113,15 @@ export const ModalAddMembers = ({
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleValidate = async () => {
|
const handleValidate = async () => {
|
||||||
|
setIsPending(true);
|
||||||
|
|
||||||
const settledPromises = await Promise.allSettled<
|
const settledPromises = await Promise.allSettled<
|
||||||
OptionInvitation | OptionNewMember
|
OptionInvitation | OptionNewMember
|
||||||
>(switchActions(selectedMembers));
|
>(switchActions(selectedMembers));
|
||||||
|
|
||||||
onClose();
|
onClose();
|
||||||
|
setIsPending(false);
|
||||||
|
|
||||||
settledPromises.forEach((settledPromise) => {
|
settledPromises.forEach((settledPromise) => {
|
||||||
switch (settledPromise.status) {
|
switch (settledPromise.status) {
|
||||||
case 'rejected':
|
case 'rejected':
|
||||||
@@ -133,7 +139,12 @@ export const ModalAddMembers = ({
|
|||||||
<Modal
|
<Modal
|
||||||
isOpen
|
isOpen
|
||||||
leftActions={
|
leftActions={
|
||||||
<Button color="secondary" fullWidth onClick={onClose}>
|
<Button
|
||||||
|
color="secondary"
|
||||||
|
fullWidth
|
||||||
|
onClick={onClose}
|
||||||
|
disabled={isPending}
|
||||||
|
>
|
||||||
{t('Cancel')}
|
{t('Cancel')}
|
||||||
</Button>
|
</Button>
|
||||||
}
|
}
|
||||||
@@ -144,7 +155,7 @@ export const ModalAddMembers = ({
|
|||||||
<Button
|
<Button
|
||||||
color="primary"
|
color="primary"
|
||||||
fullWidth
|
fullWidth
|
||||||
disabled={!selectedMembers.length}
|
disabled={!selectedMembers.length || isPending}
|
||||||
onClick={() => void handleValidate()}
|
onClick={() => void handleValidate()}
|
||||||
>
|
>
|
||||||
{t('Validate')}
|
{t('Validate')}
|
||||||
@@ -166,6 +177,7 @@ export const ModalAddMembers = ({
|
|||||||
team={team}
|
team={team}
|
||||||
setSelectedMembers={setSelectedMembers}
|
setSelectedMembers={setSelectedMembers}
|
||||||
selectedMembers={selectedMembers}
|
selectedMembers={selectedMembers}
|
||||||
|
disabled={isPending}
|
||||||
/>
|
/>
|
||||||
{selectedMembers.length > 0 && (
|
{selectedMembers.length > 0 && (
|
||||||
<Box className="mt-s">
|
<Box className="mt-s">
|
||||||
@@ -174,7 +186,7 @@ export const ModalAddMembers = ({
|
|||||||
</Text>
|
</Text>
|
||||||
<ChooseRole
|
<ChooseRole
|
||||||
currentRole={currentRole}
|
currentRole={currentRole}
|
||||||
disabled={false}
|
disabled={isPending}
|
||||||
defaultRole={Role.MEMBER}
|
defaultRole={Role.MEMBER}
|
||||||
setRole={setSelectedRole}
|
setRole={setSelectedRole}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -15,12 +15,14 @@ interface SearchMembersProps {
|
|||||||
team: Team;
|
team: Team;
|
||||||
selectedMembers: OptionsSelect;
|
selectedMembers: OptionsSelect;
|
||||||
setSelectedMembers: (value: OptionsSelect) => void;
|
setSelectedMembers: (value: OptionsSelect) => void;
|
||||||
|
disabled?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const SearchMembers = ({
|
export const SearchMembers = ({
|
||||||
team,
|
team,
|
||||||
selectedMembers,
|
selectedMembers,
|
||||||
setSelectedMembers,
|
setSelectedMembers,
|
||||||
|
disabled,
|
||||||
}: SearchMembersProps) => {
|
}: SearchMembersProps) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [input, setInput] = useState('');
|
const [input, setInput] = useState('');
|
||||||
@@ -100,6 +102,7 @@ export const SearchMembers = ({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<AsyncSelect
|
<AsyncSelect
|
||||||
|
isDisabled={disabled}
|
||||||
aria-label={t('Find a member to add to the team')}
|
aria-label={t('Find a member to add to the team')}
|
||||||
isMulti
|
isMulti
|
||||||
loadOptions={loadOptions}
|
loadOptions={loadOptions}
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ export const ModalRole = ({
|
|||||||
mutate: updateTeamAccess,
|
mutate: updateTeamAccess,
|
||||||
error: errorUpdate,
|
error: errorUpdate,
|
||||||
isError: isErrorUpdate,
|
isError: isErrorUpdate,
|
||||||
|
isPending,
|
||||||
} = useUpdateTeamAccess({
|
} = useUpdateTeamAccess({
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
toast(t('The role has been updated'), VariantType.SUCCESS, {
|
toast(t('The role has been updated'), VariantType.SUCCESS, {
|
||||||
@@ -53,7 +54,12 @@ export const ModalRole = ({
|
|||||||
<Modal
|
<Modal
|
||||||
isOpen
|
isOpen
|
||||||
leftActions={
|
leftActions={
|
||||||
<Button color="secondary" fullWidth onClick={() => onClose()}>
|
<Button
|
||||||
|
color="secondary"
|
||||||
|
fullWidth
|
||||||
|
onClick={() => onClose()}
|
||||||
|
disabled={isPending}
|
||||||
|
>
|
||||||
{t('Cancel')}
|
{t('Cancel')}
|
||||||
</Button>
|
</Button>
|
||||||
}
|
}
|
||||||
@@ -71,7 +77,7 @@ export const ModalRole = ({
|
|||||||
accessId: access.id,
|
accessId: access.id,
|
||||||
});
|
});
|
||||||
}}
|
}}
|
||||||
disabled={isNotAllowed}
|
disabled={isNotAllowed || isPending}
|
||||||
>
|
>
|
||||||
{t('Validate')}
|
{t('Validate')}
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -57,7 +57,10 @@ export const CardCreateTeam = () => {
|
|||||||
<StyledLink href="/">
|
<StyledLink href="/">
|
||||||
<Button color="secondary">{t('Cancel')}</Button>
|
<Button color="secondary">{t('Cancel')}</Button>
|
||||||
</StyledLink>
|
</StyledLink>
|
||||||
<Button onClick={() => createTeam(teamName)} disabled={!teamName}>
|
<Button
|
||||||
|
onClick={() => createTeam(teamName)}
|
||||||
|
disabled={!teamName || isPending}
|
||||||
|
>
|
||||||
{t('Create the team')}
|
{t('Create the team')}
|
||||||
</Button>
|
</Button>
|
||||||
</Box>
|
</Box>
|
||||||
|
|||||||
Reference in New Issue
Block a user