Skip to content

Commit 4330dbf

Browse files
committed
fix(web): disable buttons if insufficient balance
1 parent e72bd47 commit 4330dbf

File tree

4 files changed

+113
-55
lines changed

4 files changed

+113
-55
lines changed
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import styled from "styled-components";
2+
3+
export const ErrorButtonMessage = styled.div`
4+
display: flex;
5+
align-items: center;
6+
gap: 4px;
7+
justify-content: center;
8+
margin: 12px;
9+
color: ${({ theme }) => theme.error};
10+
font-size: 14px;
11+
`;
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
import React from "react";
2+
import styled from "styled-components";
3+
import ClosedCircle from "svgs/icons/close-circle.svg";
4+
5+
const StyledClosedCircle = styled(ClosedCircle)`
6+
path {
7+
fill: ${({ theme }) => theme.error};
8+
}
9+
`;
10+
11+
const ClosedCircleIcon: React.FC = () => {
12+
return <StyledClosedCircle />;
13+
};
14+
export default ClosedCircleIcon;

web/src/pages/Cases/CaseDetails/Appeal/Classic/Fund.tsx

Lines changed: 43 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ import { isUndefined } from "utils/index";
1515
import { wrapWithToast } from "utils/wrapWithToast";
1616

1717
import { EnsureChain } from "components/EnsureChain";
18+
import { ErrorButtonMessage } from "components/ErrorButtonMessage";
19+
import ClosedCircleIcon from "components/StyledIcons/ClosedCircleIcon";
1820

1921
const Container = styled.div`
2022
display: flex;
@@ -46,6 +48,7 @@ const StyledButton = styled(Button)`
4648
const StyledLabel = styled.label`
4749
align-self: flex-start;
4850
`;
51+
4952
const useNeedFund = () => {
5053
const { loserSideCountdown } = useCountdownContext();
5154
const { fundedChoices, winningChoice } = useFundingContext();
@@ -59,20 +62,24 @@ const useNeedFund = () => {
5962
return needFund;
6063
};
6164

62-
const useFundAppeal = (parsedAmount) => {
65+
const useFundAppeal = (parsedAmount, insufficientBalance) => {
6366
const { id } = useParams();
6467
const { selectedOption } = useSelectedOptionContext();
65-
const { data: fundAppealConfig, isError } = useSimulateDisputeKitClassicFundAppeal({
68+
const {
69+
data: fundAppealConfig,
70+
isLoading,
71+
isError,
72+
} = useSimulateDisputeKitClassicFundAppeal({
6673
query: {
67-
enabled: !isUndefined(id) && !isUndefined(selectedOption),
74+
enabled: !isUndefined(id) && !isUndefined(selectedOption) && !insufficientBalance,
6875
},
6976
args: [BigInt(id ?? 0), BigInt(selectedOption ?? 0)],
7077
value: parsedAmount,
7178
});
7279

7380
const { writeContractAsync: fundAppeal } = useWriteDisputeKitClassicFundAppeal();
7481

75-
return { fundAppeal, fundAppealConfig, isError };
82+
return { fundAppeal, fundAppealConfig, isLoading, isError };
7683
};
7784

7885
interface IFund {
@@ -98,12 +105,15 @@ const Fund: React.FC<IFund> = ({ amount, setAmount, setIsOpen }) => {
98105

99106
const parsedAmount = useParsedAmount(debouncedAmount as `${number}`);
100107

101-
const { fundAppealConfig, fundAppeal, isError } = useFundAppeal(parsedAmount);
108+
const insufficientBalance = useMemo(() => {
109+
return balance && balance.value < parsedAmount;
110+
}, [balance, parsedAmount]);
111+
112+
const { fundAppealConfig, fundAppeal, isLoading, isError } = useFundAppeal(parsedAmount, insufficientBalance);
102113

103114
const isFundDisabled = useMemo(
104-
() =>
105-
isDisconnected || isSending || !balance || parsedAmount > balance.value || Number(parsedAmount) <= 0 || isError,
106-
[isDisconnected, isSending, balance, parsedAmount, isError]
115+
() => isDisconnected || isSending || !balance || insufficientBalance || Number(parsedAmount) <= 0 || isError,
116+
[isDisconnected, isSending, balance, insufficientBalance, parsedAmount, isError]
107117
);
108118

109119
return needFund ? (
@@ -118,28 +128,33 @@ const Fund: React.FC<IFund> = ({ amount, setAmount, setIsOpen }) => {
118128
placeholder="Amount to fund"
119129
/>
120130
<EnsureChain>
121-
<StyledButton
122-
disabled={isFundDisabled}
123-
isLoading={isSending}
124-
text={isDisconnected ? "Connect to Fund" : "Fund"}
125-
onClick={() => {
126-
if (fundAppeal && fundAppealConfig && publicClient) {
127-
setIsSending(true);
128-
wrapWithToast(async () => await fundAppeal(fundAppealConfig.request), publicClient)
129-
.then((res) => {
130-
res.status && setIsOpen(true);
131-
})
132-
.finally(() => {
133-
setIsSending(false);
134-
});
135-
}
136-
}}
137-
/>
131+
<div>
132+
<StyledButton
133+
disabled={isFundDisabled}
134+
isLoading={(isSending || isLoading) && !insufficientBalance}
135+
text={isDisconnected ? "Connect to Fund" : "Fund"}
136+
onClick={() => {
137+
if (fundAppeal && fundAppealConfig && publicClient) {
138+
setIsSending(true);
139+
wrapWithToast(async () => await fundAppeal(fundAppealConfig.request), publicClient)
140+
.then((res) => {
141+
res.status && setIsOpen(true);
142+
})
143+
.finally(() => {
144+
setIsSending(false);
145+
});
146+
}
147+
}}
148+
/>
149+
{insufficientBalance && (
150+
<ErrorButtonMessage>
151+
<ClosedCircleIcon /> Insufficient balance
152+
</ErrorButtonMessage>
153+
)}
154+
</div>
138155
</EnsureChain>
139156
</Container>
140-
) : (
141-
<></>
142-
);
157+
) : null;
143158
};
144159

145160
export default Fund;

web/src/pages/Resolver/NavigationButtons/SubmitDisputeButton.tsx

Lines changed: 45 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import React, { useMemo, useState } from "react";
22
import styled from "styled-components";
33

44
import { Log, decodeEventLog, parseAbi } from "viem";
5-
import { usePublicClient } from "wagmi";
5+
import { useAccount, useBalance, usePublicClient } from "wagmi";
66

77
import { Button } from "@kleros/ui-components-library";
88

@@ -20,6 +20,9 @@ import { wrapWithToast } from "utils/wrapWithToast";
2020
import { EnsureChain } from "components/EnsureChain";
2121
import Popup, { PopupType } from "components/Popup";
2222

23+
import { ErrorButtonMessage } from "components/ErrorButtonMessage";
24+
import ClosedCircleIcon from "components/StyledIcons/ClosedCircleIcon";
25+
2326
const StyledButton = styled(Button)``;
2427

2528
const SubmitDisputeButton: React.FC = () => {
@@ -31,10 +34,18 @@ const SubmitDisputeButton: React.FC = () => {
3134
const { disputeTemplate, disputeData, resetDisputeData, isSubmittingCase, setIsSubmittingCase } =
3235
useNewDisputeContext();
3336

37+
const { address } = useAccount();
38+
const { data: userBalance, isLoading: isBalanceLoading } = useBalance({ address });
39+
40+
const insufficientBalance = useMemo(() => {
41+
const arbitrationCost = disputeData.arbitrationCost ? BigInt(disputeData.arbitrationCost) : BigInt(0);
42+
return userBalance && userBalance.value < arbitrationCost;
43+
}, [userBalance, disputeData]);
44+
3445
// TODO: decide which dispute kit to use
3546
const { data: submitCaseConfig } = useSimulateDisputeResolverCreateDisputeForTemplate({
3647
query: {
37-
enabled: isTemplateValid(disputeTemplate),
48+
enabled: !insufficientBalance && isTemplateValid(disputeTemplate),
3849
},
3950
args: [
4051
prepareArbitratorExtradata(disputeData.courtId ?? "1", disputeData.numberOfJurors ?? "", 1),
@@ -48,37 +59,44 @@ const SubmitDisputeButton: React.FC = () => {
4859
const { writeContractAsync: submitCase } = useWriteDisputeResolverCreateDisputeForTemplate();
4960

5061
const isButtonDisabled = useMemo(
51-
() => isSubmittingCase || !isTemplateValid(disputeTemplate),
52-
[isSubmittingCase, disputeTemplate]
62+
() => isSubmittingCase || !isTemplateValid(disputeTemplate) || isBalanceLoading || insufficientBalance,
63+
[isSubmittingCase, insufficientBalance, isBalanceLoading, disputeTemplate]
5364
);
5465

5566
return (
5667
<>
5768
{" "}
5869
<EnsureChain>
59-
<StyledButton
60-
text="Submit the case"
61-
disabled={isButtonDisabled}
62-
isLoading={isSubmittingCase}
63-
onClick={() => {
64-
if (submitCaseConfig) {
65-
setIsSubmittingCase(true);
66-
wrapWithToast(async () => await submitCase(submitCaseConfig.request), publicClient)
67-
.then((res) => {
68-
if (res.status && !isUndefined(res.result)) {
69-
const id = retrieveDisputeId(res.result.logs[1]);
70-
setDisputeId(Number(id));
71-
setCourtId(disputeData.courtId ?? "1");
72-
setIsPopupOpen(true);
73-
resetDisputeData();
74-
}
75-
})
76-
.finally(() => {
77-
setIsSubmittingCase(false);
78-
});
79-
}
80-
}}
81-
/>
70+
<div>
71+
<StyledButton
72+
text="Submit the case"
73+
disabled={isButtonDisabled}
74+
isLoading={(isSubmittingCase || isBalanceLoading) && !insufficientBalance}
75+
onClick={() => {
76+
if (submitCaseConfig) {
77+
setIsSubmittingCase(true);
78+
wrapWithToast(async () => await submitCase(submitCaseConfig.request), publicClient)
79+
.then((res) => {
80+
if (res.status && !isUndefined(res.result)) {
81+
const id = retrieveDisputeId(res.result.logs[1]);
82+
setDisputeId(Number(id));
83+
setCourtId(disputeData.courtId ?? "1");
84+
setIsPopupOpen(true);
85+
resetDisputeData();
86+
}
87+
})
88+
.finally(() => {
89+
setIsSubmittingCase(false);
90+
});
91+
}
92+
}}
93+
/>
94+
{insufficientBalance && (
95+
<ErrorButtonMessage>
96+
<ClosedCircleIcon /> Insufficient balance
97+
</ErrorButtonMessage>
98+
)}
99+
</div>
82100
</EnsureChain>
83101
{isPopupOpen && disputeId && (
84102
<Popup

0 commit comments

Comments
 (0)