diff --git a/web/src/components/ErrorButtonMessage.tsx b/web/src/components/ErrorButtonMessage.tsx new file mode 100644 index 000000000..ce2eff888 --- /dev/null +++ b/web/src/components/ErrorButtonMessage.tsx @@ -0,0 +1,11 @@ +import styled from "styled-components"; + +export const ErrorButtonMessage = styled.div` + display: flex; + align-items: center; + gap: 4px; + justify-content: center; + margin: 12px; + color: ${({ theme }) => theme.error}; + font-size: 14px; +`; diff --git a/web/src/components/StyledIcons/ClosedCircleIcon.tsx b/web/src/components/StyledIcons/ClosedCircleIcon.tsx new file mode 100644 index 000000000..29b7e47a3 --- /dev/null +++ b/web/src/components/StyledIcons/ClosedCircleIcon.tsx @@ -0,0 +1,14 @@ +import React from "react"; +import styled from "styled-components"; +import ClosedCircle from "svgs/icons/close-circle.svg"; + +const StyledClosedCircle = styled(ClosedCircle)` + path { + fill: ${({ theme }) => theme.error}; + } +`; + +const ClosedCircleIcon: React.FC = () => { + return ; +}; +export default ClosedCircleIcon; diff --git a/web/src/pages/Cases/CaseDetails/Appeal/Classic/Fund.tsx b/web/src/pages/Cases/CaseDetails/Appeal/Classic/Fund.tsx index 26a5cdd6c..a559aacb1 100644 --- a/web/src/pages/Cases/CaseDetails/Appeal/Classic/Fund.tsx +++ b/web/src/pages/Cases/CaseDetails/Appeal/Classic/Fund.tsx @@ -15,6 +15,8 @@ import { isUndefined } from "utils/index"; import { wrapWithToast } from "utils/wrapWithToast"; import { EnsureChain } from "components/EnsureChain"; +import { ErrorButtonMessage } from "components/ErrorButtonMessage"; +import ClosedCircleIcon from "components/StyledIcons/ClosedCircleIcon"; const Container = styled.div` display: flex; @@ -46,6 +48,7 @@ const StyledButton = styled(Button)` const StyledLabel = styled.label` align-self: flex-start; `; + const useNeedFund = () => { const { loserSideCountdown } = useCountdownContext(); const { fundedChoices, winningChoice } = useFundingContext(); @@ -59,12 +62,16 @@ const useNeedFund = () => { return needFund; }; -const useFundAppeal = (parsedAmount) => { +const useFundAppeal = (parsedAmount, insufficientBalance) => { const { id } = useParams(); const { selectedOption } = useSelectedOptionContext(); - const { data: fundAppealConfig, isError } = useSimulateDisputeKitClassicFundAppeal({ + const { + data: fundAppealConfig, + isLoading, + isError, + } = useSimulateDisputeKitClassicFundAppeal({ query: { - enabled: !isUndefined(id) && !isUndefined(selectedOption), + enabled: !isUndefined(id) && !isUndefined(selectedOption) && !insufficientBalance, }, args: [BigInt(id ?? 0), BigInt(selectedOption ?? 0)], value: parsedAmount, @@ -72,7 +79,7 @@ const useFundAppeal = (parsedAmount) => { const { writeContractAsync: fundAppeal } = useWriteDisputeKitClassicFundAppeal(); - return { fundAppeal, fundAppealConfig, isError }; + return { fundAppeal, fundAppealConfig, isLoading, isError }; }; interface IFund { @@ -98,12 +105,15 @@ const Fund: React.FC = ({ amount, setAmount, setIsOpen }) => { const parsedAmount = useParsedAmount(debouncedAmount as `${number}`); - const { fundAppealConfig, fundAppeal, isError } = useFundAppeal(parsedAmount); + const insufficientBalance = useMemo(() => { + return balance && balance.value < parsedAmount; + }, [balance, parsedAmount]); + + const { fundAppealConfig, fundAppeal, isLoading, isError } = useFundAppeal(parsedAmount, insufficientBalance); const isFundDisabled = useMemo( - () => - isDisconnected || isSending || !balance || parsedAmount > balance.value || Number(parsedAmount) <= 0 || isError, - [isDisconnected, isSending, balance, parsedAmount, isError] + () => isDisconnected || isSending || !balance || insufficientBalance || Number(parsedAmount) <= 0 || isError, + [isDisconnected, isSending, balance, insufficientBalance, parsedAmount, isError] ); return needFund ? ( @@ -118,28 +128,33 @@ const Fund: React.FC = ({ amount, setAmount, setIsOpen }) => { placeholder="Amount to fund" /> - { - if (fundAppeal && fundAppealConfig && publicClient) { - setIsSending(true); - wrapWithToast(async () => await fundAppeal(fundAppealConfig.request), publicClient) - .then((res) => { - res.status && setIsOpen(true); - }) - .finally(() => { - setIsSending(false); - }); - } - }} - /> +
+ { + if (fundAppeal && fundAppealConfig && publicClient) { + setIsSending(true); + wrapWithToast(async () => await fundAppeal(fundAppealConfig.request), publicClient) + .then((res) => { + res.status && setIsOpen(true); + }) + .finally(() => { + setIsSending(false); + }); + } + }} + /> + {insufficientBalance && ( + + Insufficient balance + + )} +
- ) : ( - <> - ); + ) : null; }; export default Fund; diff --git a/web/src/pages/Cases/CaseDetails/MaintenanceButtons/DrawButton.tsx b/web/src/pages/Cases/CaseDetails/MaintenanceButtons/DrawButton.tsx index 67b4a77e0..9f787422a 100644 --- a/web/src/pages/Cases/CaseDetails/MaintenanceButtons/DrawButton.tsx +++ b/web/src/pages/Cases/CaseDetails/MaintenanceButtons/DrawButton.tsx @@ -17,11 +17,13 @@ import { isUndefined } from "src/utils"; import { Phases } from "components/Phase"; import { IBaseMaintenanceButton } from "."; +import { Link } from "react-router-dom"; const StyledButton = styled(Button)` width: 100%; `; +const StyledLabel = styled.label``; interface IDrawButton extends IBaseMaintenanceButton { numberOfVotes?: string; period?: string; @@ -40,6 +42,11 @@ const DrawButton: React.FC = ({ id, numberOfVotes, setIsOpen, perio [maintenanceData, isDrawn, phase, period] ); + const needToPassPhase = useMemo( + () => !isUndefined(maintenanceData) && !isDrawn && period === Period.Evidence && phase !== Phases.drawing, + [maintenanceData, isDrawn, phase, period] + ); + const { data: drawConfig, isLoading: isLoadingConfig, @@ -68,7 +75,17 @@ const DrawButton: React.FC = ({ id, numberOfVotes, setIsOpen, perio setIsOpen(false); }); }; - return ; + return ( + <> + {needToPassPhase ? ( + + Jurors can be drawn in drawing phase. +
Pass phase here. +
+ ) : null} + + + ); }; export default DrawButton; diff --git a/web/src/pages/Courts/StakeMaintenanceButton/index.tsx b/web/src/pages/Courts/StakeMaintenanceButton/index.tsx index 701f110ae..128774df2 100644 --- a/web/src/pages/Courts/StakeMaintenanceButton/index.tsx +++ b/web/src/pages/Courts/StakeMaintenanceButton/index.tsx @@ -1,4 +1,4 @@ -import React, { useState } from "react"; +import React, { useEffect, useState } from "react"; import styled from "styled-components"; import DottedMenuButton from "components/DottedMenuButton"; @@ -47,6 +47,11 @@ interface IStakeMaintenanceButtons { const StakeMaintenanceButtons: React.FC = ({ className }) => { const [isOpen, setIsOpen] = useState(false); + useEffect(() => { + const openDefault = location.hash.includes("#maintenance"); + if (openDefault) setIsOpen(true); + }, []); + const toggle = () => setIsOpen((prevValue) => !prevValue); return ( diff --git a/web/src/pages/Resolver/NavigationButtons/SubmitDisputeButton.tsx b/web/src/pages/Resolver/NavigationButtons/SubmitDisputeButton.tsx index 7bb60fdd6..bcc19ef18 100644 --- a/web/src/pages/Resolver/NavigationButtons/SubmitDisputeButton.tsx +++ b/web/src/pages/Resolver/NavigationButtons/SubmitDisputeButton.tsx @@ -2,7 +2,7 @@ import React, { useMemo, useState } from "react"; import styled from "styled-components"; import { Log, decodeEventLog, parseAbi } from "viem"; -import { usePublicClient } from "wagmi"; +import { useAccount, useBalance, usePublicClient } from "wagmi"; import { Button } from "@kleros/ui-components-library"; @@ -20,6 +20,9 @@ import { wrapWithToast } from "utils/wrapWithToast"; import { EnsureChain } from "components/EnsureChain"; import Popup, { PopupType } from "components/Popup"; +import { ErrorButtonMessage } from "components/ErrorButtonMessage"; +import ClosedCircleIcon from "components/StyledIcons/ClosedCircleIcon"; + const StyledButton = styled(Button)``; const SubmitDisputeButton: React.FC = () => { @@ -31,10 +34,18 @@ const SubmitDisputeButton: React.FC = () => { const { disputeTemplate, disputeData, resetDisputeData, isSubmittingCase, setIsSubmittingCase } = useNewDisputeContext(); + const { address } = useAccount(); + const { data: userBalance, isLoading: isBalanceLoading } = useBalance({ address }); + + const insufficientBalance = useMemo(() => { + const arbitrationCost = disputeData.arbitrationCost ? BigInt(disputeData.arbitrationCost) : BigInt(0); + return userBalance && userBalance.value < arbitrationCost; + }, [userBalance, disputeData]); + // TODO: decide which dispute kit to use const { data: submitCaseConfig } = useSimulateDisputeResolverCreateDisputeForTemplate({ query: { - enabled: isTemplateValid(disputeTemplate), + enabled: !insufficientBalance && isTemplateValid(disputeTemplate), }, args: [ prepareArbitratorExtradata(disputeData.courtId ?? "1", disputeData.numberOfJurors ?? "", 1), @@ -48,37 +59,44 @@ const SubmitDisputeButton: React.FC = () => { const { writeContractAsync: submitCase } = useWriteDisputeResolverCreateDisputeForTemplate(); const isButtonDisabled = useMemo( - () => isSubmittingCase || !isTemplateValid(disputeTemplate), - [isSubmittingCase, disputeTemplate] + () => isSubmittingCase || !isTemplateValid(disputeTemplate) || isBalanceLoading || insufficientBalance, + [isSubmittingCase, insufficientBalance, isBalanceLoading, disputeTemplate] ); return ( <> {" "} - { - if (submitCaseConfig) { - setIsSubmittingCase(true); - wrapWithToast(async () => await submitCase(submitCaseConfig.request), publicClient) - .then((res) => { - if (res.status && !isUndefined(res.result)) { - const id = retrieveDisputeId(res.result.logs[1]); - setDisputeId(Number(id)); - setCourtId(disputeData.courtId ?? "1"); - setIsPopupOpen(true); - resetDisputeData(); - } - }) - .finally(() => { - setIsSubmittingCase(false); - }); - } - }} - /> +
+ { + if (submitCaseConfig) { + setIsSubmittingCase(true); + wrapWithToast(async () => await submitCase(submitCaseConfig.request), publicClient) + .then((res) => { + if (res.status && !isUndefined(res.result)) { + const id = retrieveDisputeId(res.result.logs[1]); + setDisputeId(Number(id)); + setCourtId(disputeData.courtId ?? "1"); + setIsPopupOpen(true); + resetDisputeData(); + } + }) + .finally(() => { + setIsSubmittingCase(false); + }); + } + }} + /> + {insufficientBalance && ( + + Insufficient balance + + )} +
{isPopupOpen && disputeId && (