Skip to content

Commit df023b9

Browse files
fix: [IOBP-1986] button loader indicator (#344)
* fix: [IOBP-1986] add spinner indicator to submit buttons * fix: [IOBP-1986] fix size and alignment of the spinner in buttons * fix: [IOBP-1986] disable button approve test if conventiondetails is in reject mode * fix: [IOBP-1986] adjust style * fix: [IOBP-1986] add spinner indicator to other submit buttons * chore: adjust type props * feat: [IOBP-1986] add reusable Button component including spinner and title handling * chore: [IOBP-1986] update Button component name to AsyncButton for clarity
1 parent 77b2535 commit df023b9

File tree

20 files changed

+574
-359
lines changed

20 files changed

+574
-359
lines changed
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import { Button as ButtonDesignKit, ButtonProps } from "design-react-kit";
2+
import { PropsWithChildren } from "react";
3+
import SmallSpinner from "../SmallSpinner/SmallSpinner";
4+
5+
type Props = PropsWithChildren & {
6+
isPending?: boolean;
7+
fullwidth?: boolean;
8+
} & ButtonProps;
9+
10+
const AsyncButton = ({
11+
children,
12+
isPending,
13+
fullwidth,
14+
className,
15+
disabled,
16+
...rest
17+
}: Props) => {
18+
const classNames = `${className} ${fullwidth ? "w-100" : ""}`.trim();
19+
return (
20+
<ButtonDesignKit
21+
type="submit"
22+
className={classNames}
23+
tag="button"
24+
disabled={disabled || isPending}
25+
{...rest}
26+
>
27+
<div className="d-flex align-items-center justify-content-center">
28+
{isPending && <SmallSpinner />}
29+
{children}
30+
</div>
31+
</ButtonDesignKit>
32+
);
33+
};
34+
35+
export default AsyncButton;

src/components/Discounts/DeleteModal.tsx

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,35 +1,36 @@
11
import { Button } from "design-react-kit";
22
import { Modal, ModalHeader, ModalBody, ModalFooter } from "reactstrap";
3+
import AsyncButton from "../AsyncButton/AsyncButton";
4+
import { ModalProps } from "../../types";
35

46
export function DeleteModal({
57
isOpen,
68
onToggle,
7-
onDelete
8-
}: {
9-
isOpen: boolean;
10-
onToggle(): void;
11-
onDelete(): void;
12-
}) {
9+
actionRequest,
10+
isPending
11+
}: ModalProps) {
1312
return (
1413
<Modal isOpen={isOpen} toggle={onToggle}>
1514
<ModalHeader toggle={onToggle}>Elimina opportunità</ModalHeader>
1615
<ModalBody>Sei sicuro di voler eliminare questa opportunità?</ModalBody>
1716
<ModalFooter className="d-flex flex-column">
18-
<Button
17+
<AsyncButton
1918
color="primary"
2019
onClick={() => {
21-
onDelete();
20+
actionRequest();
2221
onToggle();
2322
}}
24-
style={{ width: "100%" }}
23+
fullwidth
24+
isPending={isPending}
2525
>
2626
Elimina
27-
</Button>{" "}
27+
</AsyncButton>
2828
<Button
2929
color="primary"
3030
outline
31+
tag="button"
3132
onClick={onToggle}
32-
style={{ width: "100%" }}
33+
className="w-100"
3334
>
3435
Annulla
3536
</Button>

src/components/Discounts/Discounts.tsx

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -250,27 +250,29 @@ const Discounts = () => {
250250
<div>
251251
<PublishModal
252252
isOpen={publishModal}
253-
toggle={closeActionModal}
254-
publish={() => {
253+
onToggle={closeActionModal}
254+
isPending={publishDiscountMutation.isPending}
255+
actionRequest={() => {
255256
if (selectedDiscountAction) {
256257
publishDiscount(selectedDiscountAction.discountId);
257258
}
258259
}}
259-
profile={profile}
260260
/>
261261
<UnpublishModal
262262
isOpen={unpublishModal}
263-
toggle={closeActionModal}
264-
unpublish={() => {
263+
onToggle={closeActionModal}
264+
isPending={unpublishDiscountMutation.isPending}
265+
actionRequest={() => {
265266
if (selectedDiscountAction) {
266267
unpublishDiscount(selectedDiscountAction.discountId);
267268
}
268269
}}
269270
/>
270271
<TestModal
271272
isOpen={testModal}
272-
toggle={closeActionModal}
273-
testRequest={() => {
273+
onToggle={closeActionModal}
274+
isPending={testDiscountMutation.isPending}
275+
actionRequest={() => {
274276
if (selectedDiscountAction) {
275277
testDiscount(selectedDiscountAction.discountId);
276278
}
@@ -279,7 +281,8 @@ const Discounts = () => {
279281
<DeleteModal
280282
isOpen={deleteModal}
281283
onToggle={closeActionModal}
282-
onDelete={() => {
284+
isPending={deleteDiscountMutation.isPending}
285+
actionRequest={() => {
283286
if (selectedDiscountAction) {
284287
deleteDiscount(selectedDiscountAction.discountId);
285288
}

src/components/Discounts/PublishModal.tsx

Lines changed: 22 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,39 @@
1-
import { Button } from "design-react-kit";
21
import { Modal, ModalHeader, ModalBody, ModalFooter } from "reactstrap";
3-
import { Profile } from "../../api/generated";
4-
5-
type Props = {
6-
isOpen: boolean;
7-
toggle(): void;
8-
publish(): void;
9-
profile?: Profile;
10-
};
2+
import { Button } from "design-react-kit";
3+
import { ModalProps } from "../../types";
4+
import AsyncButton from "../AsyncButton/AsyncButton";
115

12-
function PublishModal({ isOpen, toggle, publish, profile }: Props) {
6+
function PublishModal({
7+
isOpen,
8+
onToggle,
9+
actionRequest,
10+
isPending
11+
}: ModalProps) {
1312
return (
14-
<Modal isOpen={isOpen} toggle={toggle} size="md">
15-
<ModalHeader toggle={toggle}>Pubblica opportunità</ModalHeader>
13+
<Modal isOpen={isOpen} toggle={onToggle} size="md">
14+
<ModalHeader toggle={onToggle}>Pubblica opportunità</ModalHeader>
1615
<ModalBody>
17-
{profile && profile.salesChannel.channelType !== "OfflineChannel"
18-
? "Se pubblichi, l’opportunità diventerà visibile su App IO dai beneficiari di Carta Giovani Nazionale."
19-
: "Hai informato il personale addetto alle casse o alla relazione col pubblico? Se pubblichi, l’opportunità diventerà visibile su App IO dai beneficiari di Carta Giovani Nazionale."}
16+
Se pubblichi, l’opportunità diventerà visibile su App IO dai beneficiari
17+
di Carta Giovani Nazionale.
2018
</ModalBody>
2119
<ModalFooter className="d-flex flex-column">
22-
<Button
20+
<AsyncButton
2321
color="primary"
2422
onClick={() => {
25-
toggle();
26-
publish();
23+
onToggle();
24+
actionRequest();
2725
}}
28-
style={{ width: "100%" }}
26+
isPending={isPending}
27+
fullwidth
2928
>
3029
Sì, pubblica
31-
</Button>{" "}
30+
</AsyncButton>
3231
<Button
3332
color="primary"
33+
tag="button"
3434
outline
35-
onClick={toggle}
36-
style={{ width: "100%" }}
35+
onClick={onToggle}
36+
className="w-100"
3737
>
3838
No, torna indietro
3939
</Button>

src/components/Discounts/TestModal.tsx

Lines changed: 20 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,39 @@
1-
import { Button } from "design-react-kit";
21
import { Modal, ModalHeader, ModalBody, ModalFooter } from "reactstrap";
2+
import { Button } from "design-react-kit";
3+
import { ModalProps } from "../../types";
4+
import AsyncButton from "../AsyncButton/AsyncButton";
35

4-
type Props = {
5-
isOpen: boolean;
6-
toggle(): void;
7-
testRequest(): void;
8-
};
9-
10-
const TestModal = ({ isOpen, toggle, testRequest }: Props) => (
11-
<Modal isOpen={isOpen} toggle={toggle} size="md">
12-
<ModalHeader toggle={toggle}>Richiedi test</ModalHeader>
6+
const TestModal = ({
7+
isOpen,
8+
onToggle,
9+
actionRequest,
10+
isPending
11+
}: ModalProps) => (
12+
<Modal isOpen={isOpen} toggle={onToggle} size="md">
13+
<ModalHeader toggle={onToggle}>Richiedi test</ModalHeader>
1314
<ModalBody>
1415
Se confermi, dichiari di avere concluso le implementazioni tecniche
1516
necessarie e il team di CGN procederà con un test funzionale secondo la
1617
modalità di riconoscimento che avete scelto.
1718
</ModalBody>
1819
<ModalFooter className="d-flex flex-column">
19-
<Button
20+
<AsyncButton
2021
color="primary"
2122
onClick={() => {
22-
toggle();
23-
testRequest();
23+
onToggle();
24+
actionRequest();
2425
}}
25-
style={{ width: "100%" }}
26+
fullwidth
27+
isPending={isPending}
2628
>
2729
Conferma richiesta
28-
</Button>{" "}
30+
</AsyncButton>
2931
<Button
3032
color="primary"
33+
tag="button"
3134
outline
32-
onClick={toggle}
33-
style={{ width: "100%" }}
35+
onClick={onToggle}
36+
className="w-100"
3437
>
3538
Annulla
3639
</Button>

src/components/Discounts/UnpublishModal.tsx

Lines changed: 20 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,40 @@
1-
import { Button } from "design-react-kit";
21
import { Modal, ModalHeader, ModalBody, ModalFooter } from "reactstrap";
2+
import { Button } from "design-react-kit";
3+
import { ModalProps } from "../../types";
4+
import AsyncButton from "../AsyncButton/AsyncButton";
35

4-
type Props = {
5-
isOpen: boolean;
6-
toggle(): void;
7-
unpublish(): void;
8-
};
9-
10-
function UnpublishModal({ isOpen, toggle, unpublish }: Props) {
6+
function UnpublishModal({
7+
isOpen,
8+
onToggle,
9+
actionRequest,
10+
isPending
11+
}: ModalProps) {
1112
return (
12-
<Modal isOpen={isOpen} toggle={toggle} size="md">
13-
<ModalHeader toggle={toggle}>Sospendi opportunità</ModalHeader>
13+
<Modal isOpen={isOpen} toggle={onToggle} size="md">
14+
<ModalHeader toggle={onToggle}>Sospendi opportunità</ModalHeader>
1415
<ModalBody>
1516
Sei sicuro di voler riportare in bozza questa opportunità? Se non hai
1617
altre opportunità pubblicate in questo momento, non sarai più visibile
1718
nella lista degli operatori aderenti all&lsquo;iniziativa.
1819
</ModalBody>
1920
<ModalFooter className="d-flex flex-column">
20-
<Button
21+
<AsyncButton
2122
color="danger"
2223
onClick={() => {
23-
toggle();
24-
unpublish();
24+
onToggle();
25+
actionRequest();
2526
}}
26-
style={{ width: "100%" }}
27+
fullwidth
28+
isPending={isPending}
2729
>
2830
Torna in bozza
29-
</Button>{" "}
31+
</AsyncButton>
3032
<Button
3133
color="primary"
34+
tag="button"
3235
outline
33-
onClick={toggle}
34-
style={{ width: "100%" }}
36+
onClick={onToggle}
37+
className="w-100"
3538
>
3639
Annulla
3740
</Button>

src/components/Form/CreateEditActivationForm.tsx

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import { useHistory, useParams } from "react-router-dom";
22
import z from "zod/v4";
33
import { useMemo } from "react";
4-
import { Button, Icon } from "design-react-kit";
4+
import { Icon } from "design-react-kit";
55
import { useFieldArray } from "@hookform/lenses/rhf";
6+
import { Button } from "design-react-kit";
67
import { Severity, useTooltip } from "../../context/tooltip";
78
import { remoteData } from "../../api/common";
89
import { ADMIN_PANEL_ACCESSI } from "../../navigation/routes";
@@ -14,6 +15,7 @@ import {
1415
OrganizationWithReferents
1516
} from "../../api/generated_backoffice";
1617
import PlusCircleIcon from "../../assets/icons/plus-circle.svg?react";
18+
import AsyncButton from "../AsyncButton/AsyncButton";
1719
import { activationValidationSchema } from "./ValidationSchemas";
1820
import FormSection from "./FormSection";
1921
import FormField from "./FormField";
@@ -102,6 +104,9 @@ const CreateEditActivationForm = () => {
102104

103105
const canChangeEntityType = !operatorFiscalCode;
104106

107+
const isMutating =
108+
form.formState.isSubmitting || upsertActivationMutation.isPending;
109+
105110
return (
106111
<form
107112
autoComplete="off"
@@ -260,17 +265,14 @@ const CreateEditActivationForm = () => {
260265
>
261266
Indietro
262267
</Button>
263-
<Button
268+
<AsyncButton
264269
type="submit"
265270
className="px-14"
266271
color="primary"
267-
disabled={
268-
form.formState.isSubmitting || upsertActivationMutation.isPending
269-
}
270-
tag="button"
272+
isPending={isMutating}
271273
>
272274
Salva
273-
</Button>
275+
</AsyncButton>
274276
</div>
275277
</FormSection>
276278
</form>

0 commit comments

Comments
 (0)