diff --git a/bot-pinner/README.md b/bot-pinner/README.md
index 76704e415..7f0edf6ec 100644
--- a/bot-pinner/README.md
+++ b/bot-pinner/README.md
@@ -17,6 +17,7 @@ $ docker-compose up -d
2. Evidence container that awaits new events and then scrapes the latest hashes and submits it to IPFS.
3. `src/peers.txt` contains a list of peers, by default it will add connects to Estuary & Kleros IPFS nodes. This should make it easier to find content by creating a data network around Kleros Court V2.
1. Adding these peers will make it easier to find and replicate content.
+
## DappNode
:warning: For the following steps, you need access to [a DappNode](https://dappnode.io) system with the IPFS service running.
diff --git a/web/.parcelrc b/web/.parcelrc
index b812a5214..8b6b0a40c 100644
--- a/web/.parcelrc
+++ b/web/.parcelrc
@@ -1,6 +1,8 @@
{
"extends": "@parcel/config-default",
"transformers": {
- "*.svg": ["...", "@parcel/transformer-svg-react"]
+ "*.svg": ["...", "@parcel/transformer-svg-react"],
+ "tsx:*.svg": ["@parcel/transformer-svg-react"],
+ "tsx:*": ["..."]
}
}
diff --git a/web/src/assets/svgs/icons/appeal.png b/web/src/assets/svgs/icons/appeal.png
new file mode 100644
index 000000000..d0faae682
Binary files /dev/null and b/web/src/assets/svgs/icons/appeal.png differ
diff --git a/web/src/assets/svgs/icons/appeal.svg b/web/src/assets/svgs/icons/appeal.svg
new file mode 100644
index 000000000..a5afa1e66
--- /dev/null
+++ b/web/src/assets/svgs/icons/appeal.svg
@@ -0,0 +1,9 @@
+
+
+
+
+
+
+
+
+
diff --git a/web/src/assets/svgs/icons/balance.svg b/web/src/assets/svgs/icons/balance.svg
new file mode 100644
index 000000000..25c5a37f9
--- /dev/null
+++ b/web/src/assets/svgs/icons/balance.svg
@@ -0,0 +1,11 @@
+
+
+
+
+
+
+
+
+
+
+
diff --git a/web/src/assets/svgs/icons/info-circle.svg b/web/src/assets/svgs/icons/info-circle.svg
new file mode 100644
index 000000000..bcb80ff1e
--- /dev/null
+++ b/web/src/assets/svgs/icons/info-circle.svg
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/web/src/assets/svgs/icons/kleros.svg b/web/src/assets/svgs/icons/kleros.svg
index 532aaf497..e0dc1a0fd 100644
--- a/web/src/assets/svgs/icons/kleros.svg
+++ b/web/src/assets/svgs/icons/kleros.svg
@@ -1,3 +1,3 @@
-
+
diff --git a/web/src/assets/svgs/icons/voted.svg b/web/src/assets/svgs/icons/voted.svg
new file mode 100644
index 000000000..d43f24ad6
--- /dev/null
+++ b/web/src/assets/svgs/icons/voted.svg
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/web/src/components/DisputeCard/index.tsx b/web/src/components/DisputeCard/index.tsx
index c6ba8f6cb..5470894c3 100644
--- a/web/src/components/DisputeCard/index.tsx
+++ b/web/src/components/DisputeCard/index.tsx
@@ -34,7 +34,11 @@ const Container = styled.div`
}
`;
-const getPeriodEndTimestamp = (lastPeriodChange: string, currentPeriodIndex: number, timesPerPeriod: string[]) => {
+export const getPeriodEndTimestamp = (
+ lastPeriodChange: string,
+ currentPeriodIndex: number,
+ timesPerPeriod: string[]
+) => {
const durationCurrentPeriod = parseInt(timesPerPeriod[currentPeriodIndex]);
return parseInt(lastPeriodChange) + durationCurrentPeriod;
};
diff --git a/web/src/components/Popup/Description/Appeal.tsx b/web/src/components/Popup/Description/Appeal.tsx
new file mode 100644
index 000000000..e994b96c1
--- /dev/null
+++ b/web/src/components/Popup/Description/Appeal.tsx
@@ -0,0 +1,51 @@
+import React from "react";
+import styled from "styled-components";
+
+const Container = styled.div`
+ display: flex;
+ flex-direction: column;
+`;
+
+const StyledAmountFunded = styled.div`
+ display: flex;
+ margin-left: calc(8px + (44 - 8) * ((100vw - 300px) / (1250 - 300)));
+ margin-right: calc(8px + (44 - 8) * ((100vw - 300px) / (1250 - 300)));
+ color: ${({ theme }) => theme.secondaryText};
+ text-align: center;
+`;
+
+const StyledOptionFunded = styled.div`
+ display: flex;
+ margin-bottom: calc(16px + (32 - 16) * ((100vw - 300px) / (1250 - 300)));
+ margin-left: calc(8px + (44 - 8) * ((100vw - 300px) / (1250 - 300)));
+ margin-right: calc(8px + (44 - 8) * ((100vw - 300px) / (1250 - 300)));
+ color: ${({ theme }) => theme.secondaryText};
+ text-align: center;
+`;
+
+const AmountContainer = styled.div`
+ color: ${({ theme }) => theme.primaryText};
+`;
+
+const OptionContainer = styled.div`
+ color: ${({ theme }) => theme.primaryText};
+`;
+
+interface IAppeal {
+ amount: string;
+ option: string;
+}
+
+const Appeal: React.FC = ({ amount, option }) => {
+ return (
+
+
+ You have funded: {amount} ETH
+
+
+ Option funded: {option}
+
+
+ );
+};
+export default Appeal;
diff --git a/web/src/components/Popup/Description/StakeWithdraw.tsx b/web/src/components/Popup/Description/StakeWithdraw.tsx
new file mode 100644
index 000000000..960d817b8
--- /dev/null
+++ b/web/src/components/Popup/Description/StakeWithdraw.tsx
@@ -0,0 +1,94 @@
+import React from "react";
+import styled from "styled-components";
+import { isUndefined } from "utils/index";
+import { useAccount } from "wagmi";
+import { useKlerosCoreGetJurorBalance } from "hooks/contracts/generated";
+import { format } from "src/pages/Dashboard/Courts/CourtCard";
+import KlerosLogo from "tsx:svgs/icons/kleros.svg";
+
+const Container = styled.div`
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+`;
+
+const StyledKlerosLogo = styled(KlerosLogo)`
+ width: 14px;
+ height: 14px;
+`;
+
+const StyledTitle = styled.div`
+ display: flex;
+ margin-bottom: calc(16px + (32 - 16) * ((100vw - 300px) / (1250 - 300)));
+ margin-left: calc(8px + (44 - 8) * ((100vw - 300px) / (1250 - 300)));
+ margin-right: calc(8px + (44 - 8) * ((100vw - 300px) / (1250 - 300)));
+ color: ${({ theme }) => theme.secondaryText};
+ text-align: center;
+`;
+
+const AmountStakedOrWithdrawnContainer = styled.div`
+ font-size: 24px;
+ font-weight: 600;
+ color: ${({ theme }) => theme.secondaryPurple};
+ margin-bottom: calc(0px + (4 - 0) * ((100vw - 300px) / (1250 - 300)));
+`;
+
+const TotalStakeContainer = styled.div`
+ display: flex;
+ font-size: 14px;
+ align-items: center;
+ justify-content: center;
+ margin-bottom: calc(8px + (32 - 8) * ((100vw - 300px) / (1250 - 300)));
+`;
+
+const MyStakeContainer = styled.div`
+ display: flex;
+ margin: 0px calc(4px + (8 - 4) * ((100vw - 300px) / (1250 - 300)));
+ color: ${({ theme }) => theme.secondaryText};
+`;
+
+const AmountContainer = styled.div`
+ font-weight: 600;
+ color: ${({ theme }) => theme.primaryText};
+`;
+
+interface IStakeWithdraw {
+ pnkStaked: string;
+ courtName: string;
+ isStake: boolean;
+ courtId: string;
+}
+
+interface IAmountStakedOrWithdrawn {
+ pnkStaked: string;
+ isStake: boolean;
+}
+
+const AmountStakedOrWithdrawn: React.FC = ({ pnkStaked, isStake }) => {
+ return isStake ? + {pnkStaked} PNK
: - {pnkStaked} PNK
;
+};
+
+const StakeWithdraw: React.FC = ({ pnkStaked, courtName, isStake, courtId }) => {
+ const { address } = useAccount();
+
+ const { data: jurorBalance } = useKlerosCoreGetJurorBalance({
+ enabled: !isUndefined(address) && !isUndefined(courtId),
+ args: [address, BigInt(courtId)],
+ watch: true,
+ });
+
+ return (
+
+ 🎉 Your stake in the {courtName} court was successful! 🎉
+
+
+
+
+
+ My Stake: {" "}
+ {`${format(jurorBalance?.[0])} PNK`}
+
+
+ );
+};
+export default StakeWithdraw;
diff --git a/web/src/components/Popup/Description/VoteWithCommit.tsx b/web/src/components/Popup/Description/VoteWithCommit.tsx
new file mode 100644
index 000000000..789840385
--- /dev/null
+++ b/web/src/components/Popup/Description/VoteWithCommit.tsx
@@ -0,0 +1,39 @@
+import React from "react";
+import styled from "styled-components";
+
+const Container = styled.div`
+ display: flex;
+ flex-direction: column;
+`;
+
+const StyledDescription = styled.div`
+ margin-bottom: calc(16px + (32 - 16) * ((100vw - 300px) / (1250 - 300)));
+ margin-left: calc(8px + (32 - 8) * ((100vw - 300px) / (1250 - 300)));
+ margin-right: calc(8px + (32 - 8) * ((100vw - 300px) / (1250 - 300)));
+ color: ${({ theme }) => theme.secondaryText};
+ text-align: center;
+ line-height: 21.8px;
+`;
+
+const EmphasizedDate = styled.span`
+ font-size: 16px;
+ font-weight: 400;
+ line-height: 21.8px;
+ color: ${({ theme }) => theme.primaryText};
+`;
+
+interface IVoteWithCommit {
+ date: string;
+}
+
+const VoteWithCommit: React.FC = ({ date }) => {
+ return (
+
+
+ Your vote is confirmed. It's kept secret until all jurors have cast their votes.
+ You'll need to justify and reveal your vote on {date}
+
+
+ );
+};
+export default VoteWithCommit;
diff --git a/web/src/components/Popup/Description/VoteWithoutCommit.tsx b/web/src/components/Popup/Description/VoteWithoutCommit.tsx
new file mode 100644
index 000000000..a5b445130
--- /dev/null
+++ b/web/src/components/Popup/Description/VoteWithoutCommit.tsx
@@ -0,0 +1,39 @@
+import React from "react";
+import styled from "styled-components";
+
+const Container = styled.div`
+ display: flex;
+ flex-direction: column;
+`;
+
+const StyledDescription = styled.div`
+ margin-bottom: calc(16px + (32 - 16) * ((100vw - 300px) / (1250 - 300)));
+ margin-left: calc(8px + (32 - 8) * ((100vw - 300px) / (1250 - 300)));
+ margin-right: calc(8px + (32 - 8) * ((100vw - 300px) / (1250 - 300)));
+ color: ${({ theme }) => theme.secondaryText};
+ text-align: center;
+ line-height: 21.8px;
+`;
+
+const EmphasizedDate = styled.span`
+ font-size: 16px;
+ font-weight: 400;
+ line-height: 21.8px;
+ color: ${({ theme }) => theme.primaryText};
+`;
+
+interface IVoteWithoutCommit {
+ date: string;
+}
+
+const VoteWithoutCommit: React.FC = ({ date }) => {
+ return (
+
+
+ The decision date is {date} with the possibility for appeals. After that time
+ you will be informed about the jury decision.
+
+
+ );
+};
+export default VoteWithoutCommit;
diff --git a/web/src/components/Popup/ExtraInfo/StakeWithdrawExtraInfo.tsx b/web/src/components/Popup/ExtraInfo/StakeWithdrawExtraInfo.tsx
new file mode 100644
index 000000000..5ca4b4169
--- /dev/null
+++ b/web/src/components/Popup/ExtraInfo/StakeWithdrawExtraInfo.tsx
@@ -0,0 +1,22 @@
+import React from "react";
+import styled from "styled-components";
+
+const Container = styled.div`
+ display: flex;
+ color: ${({ theme }) => theme.secondaryText};
+ text-align: center;
+ margin-left: calc(8px + (44 - 8) * ((100vw - 300px) / (1250 - 300)));
+ margin-right: calc(8px + (44 - 8) * ((100vw - 300px) / (1250 - 300)));
+ margin-top: calc(8px + (24 - 8) * ((100vw - 300px) / (1250 - 300)));
+`;
+
+const StakeWithdrawExtraInfo: React.FC = () => {
+ return (
+
+ {
+ "In order not to miss when you're drawn for cases, make sure to subscribe to notifications: Settings > Notifications"
+ }
+
+ );
+};
+export default StakeWithdrawExtraInfo;
diff --git a/web/src/components/Popup/ExtraInfo/VoteWithCommitExtraInfo.tsx b/web/src/components/Popup/ExtraInfo/VoteWithCommitExtraInfo.tsx
new file mode 100644
index 000000000..199264b3d
--- /dev/null
+++ b/web/src/components/Popup/ExtraInfo/VoteWithCommitExtraInfo.tsx
@@ -0,0 +1,33 @@
+import React from "react";
+import styled from "styled-components";
+import InfoCircle from "tsx:svgs/icons/info-circle.svg";
+
+const Container = styled.div`
+ display: flex;
+ gap: 8px;
+ text-align: center;
+ align-items: center;
+ margin: 0 calc(8px + (32 - 8) * ((100vw - 300px) / (1250 - 300)));
+ margin-top: calc(8px + (24 - 8) * ((100vw - 300px) / (1250 - 300)));
+ small {
+ font-size: 14px;
+ font-weight: 400;
+ color: ${({ theme }) => theme.secondaryText};
+ line-height: 19px;
+ }
+`;
+
+const StyledInfoCircle = styled(InfoCircle)`
+ min-width: 16px;
+ min-height: 16px;
+`;
+
+const VoteWithCommitExtraInfo: React.FC = () => {
+ return (
+
+
+ Subscribe to receive notifications to be reminded when the reveal time comes.
+
+ );
+};
+export default VoteWithCommitExtraInfo;
diff --git a/web/src/components/Popup/index.tsx b/web/src/components/Popup/index.tsx
new file mode 100644
index 000000000..f9433f1a2
--- /dev/null
+++ b/web/src/components/Popup/index.tsx
@@ -0,0 +1,171 @@
+import React, { useRef } from "react";
+import styled from "styled-components";
+import { Button } from "@kleros/ui-components-library";
+import { Overlay } from "components/Overlay";
+import StakeWithdraw from "./Description/StakeWithdraw";
+import VoteWithCommit from "./Description/VoteWithCommit";
+import VoteWithoutCommit from "./Description/VoteWithoutCommit";
+import Appeal from "./Description/Appeal";
+import VoteWithCommitExtraInfo from "./ExtraInfo/VoteWithCommitExtraInfo";
+import StakeWithdrawExtraInfo from "./ExtraInfo/StakeWithdrawExtraInfo";
+
+export enum PopupType {
+ STAKE_WITHDRAW = "STAKE_WITHDRAW",
+ APPEAL = "APPEAL",
+ VOTE_WITHOUT_COMMIT = "VOTE_WITHOUT_COMMIT",
+ VOTE_WITH_COMMIT = "VOTE_WITH_COMMIT",
+}
+
+interface IStakeWithdraw {
+ popupType: PopupType.STAKE_WITHDRAW;
+ pnkStaked: string;
+ courtName: string;
+ isStake: boolean;
+ courtId: string;
+}
+
+interface IVoteWithoutCommit {
+ popupType: PopupType.VOTE_WITHOUT_COMMIT;
+ date: string;
+}
+
+interface IVoteWithCommit {
+ popupType: PopupType.VOTE_WITH_COMMIT;
+ date: string;
+}
+
+interface IAppeal {
+ popupType: PopupType.APPEAL;
+ amount: string;
+ option: string;
+}
+interface IPopup {
+ title: string;
+ icon: React.FC>;
+ popupType: PopupType;
+ setIsOpen: (val: boolean) => void;
+ setAmount?: (val: string) => void;
+ isCommit?: boolean;
+}
+
+type PopupProps = IStakeWithdraw | IVoteWithoutCommit | IVoteWithCommit | IAppeal;
+
+const Header = styled.h1`
+ display: flex;
+ margin-top: calc(12px + (32 - 12) * ((100vw - 300px) / (1250 - 300)));
+ margin-bottom: calc(12px + (24 - 12) * ((100vw - 300px) / (1250 - 300)));
+ font-size: 24px;
+ font-weight: 600;
+ line-height: 32.68px;
+`;
+
+const IconContainer = styled.div`
+ width: calc(150px + (350 - 150) * (100vw - 375px) / (1250 - 375));
+ display: flex;
+ align-items: center;
+ justify-content: center;
+
+ svg {
+ display: inline-block;
+ width: calc(150px + (350 - 150) * (100vw - 375px) / (1250 - 375));
+ height: calc(150px + (350 - 150) * (100vw - 375px) / (1250 - 375));
+ }
+`;
+
+const StyledButton = styled(Button)`
+ margin: calc(16px + (32 - 16) * ((100vw - 300px) / (1250 - 300)));
+`;
+
+const Container = styled.div`
+ display: flex;
+ position: fixed;
+ top: 50%;
+ left: 50%;
+ transform: translate(-50%, -50%);
+
+ z-index: 10;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ width: calc(300px + (600 - 300) * (100vw - 375px) / (1250 - 375));
+ max-width: 600px;
+ border-radius: 3px;
+ border: 1px solid ${({ theme }) => theme.stroke};
+ background-color: ${({ theme }) => theme.whiteBackground};
+ box-shadow: 0px 2px 3px rgba(0, 0, 0, 0.06);
+
+ svg {
+ visibility: visible;
+ }
+`;
+
+const Popup: React.FC = ({
+ title,
+ icon: Icon,
+ popupType,
+ setIsOpen,
+ setAmount,
+ isCommit,
+ ...props
+}) => {
+ const containerRef = useRef(null);
+
+ const resetValue = () => {
+ if (setAmount) {
+ setAmount("");
+ }
+ };
+
+ let PopupComponent: JSX.Element | null = null;
+
+ switch (popupType) {
+ case PopupType.STAKE_WITHDRAW: {
+ const { pnkStaked, courtName, isStake, courtId } = props as IStakeWithdraw;
+ PopupComponent = (
+
+ );
+ break;
+ }
+ case PopupType.VOTE_WITHOUT_COMMIT: {
+ const { date } = props as IVoteWithoutCommit;
+ PopupComponent = ;
+ break;
+ }
+ case PopupType.VOTE_WITH_COMMIT: {
+ const { date } = props as IVoteWithCommit;
+ PopupComponent = ;
+ break;
+ }
+ case PopupType.APPEAL: {
+ const { amount, option } = props as IAppeal;
+ PopupComponent = ;
+ break;
+ }
+ default:
+ break;
+ }
+
+ return (
+ <>
+
+
+
+ {PopupComponent}
+
+
+
+ {popupType === PopupType.STAKE_WITHDRAW && }
+ {popupType === PopupType.VOTE_WITH_COMMIT && }
+ {
+ setIsOpen(false);
+ resetValue();
+ }}
+ />
+
+ >
+ );
+};
+export default Popup;
diff --git a/web/src/pages/Cases/CaseDetails/Appeal/Classic/Fund.tsx b/web/src/pages/Cases/CaseDetails/Appeal/Classic/Fund.tsx
index 3888b2b25..12ab41015 100644
--- a/web/src/pages/Cases/CaseDetails/Appeal/Classic/Fund.tsx
+++ b/web/src/pages/Cases/CaseDetails/Appeal/Classic/Fund.tsx
@@ -42,7 +42,13 @@ const useFundAppeal = (parsedAmount) => {
return fundAppeal;
};
-const Fund: React.FC = () => {
+interface IFund {
+ amount: string;
+ setAmount: (val: string) => void;
+ setIsOpen: (val: boolean) => void;
+}
+
+const Fund: React.FC = ({ amount, setAmount, setIsOpen }) => {
const needFund = useNeedFund();
const { address, isDisconnected } = useAccount();
const { data: balance } = useBalance({
@@ -51,7 +57,6 @@ const Fund: React.FC = () => {
});
const publicClient = usePublicClient();
- const [amount, setAmount] = useState("");
const [debouncedAmount, setDebouncedAmount] = useState("");
useDebounce(() => setDebouncedAmount(amount), 500, [amount]);
@@ -79,12 +84,12 @@ const Fund: React.FC = () => {
onClick={() => {
if (fundAppeal) {
setIsSending(true);
- wrapWithToast(async () => await fundAppeal().then((response) => response.hash), publicClient)
- .then(() => {
- setAmount("");
- close();
- })
- .finally(() => setIsSending(false));
+ wrapWithToast(async () => await fundAppeal().then((response) => response.hash), publicClient).finally(
+ () => {
+ setIsSending(false);
+ setIsOpen(true);
+ }
+ );
}
}}
/>
diff --git a/web/src/pages/Cases/CaseDetails/Appeal/Classic/index.tsx b/web/src/pages/Cases/CaseDetails/Appeal/Classic/index.tsx
index 657f6748d..d5020a627 100644
--- a/web/src/pages/Cases/CaseDetails/Appeal/Classic/index.tsx
+++ b/web/src/pages/Cases/CaseDetails/Appeal/Classic/index.tsx
@@ -1,15 +1,44 @@
-import React from "react";
-import { ClassicAppealProvider } from "hooks/useClassicAppealContext";
+import React, { useState } from "react";
+import { ClassicAppealProvider, useOptionsContext, useSelectedOptionContext } from "hooks/useClassicAppealContext";
import Options from "./Options";
import Fund from "./Fund";
+import Popup, { PopupType } from "components/Popup";
+import AppealIcon from "svgs/icons/appeal.svg";
+import { isUndefined } from "utils/index";
-const Classic: React.FC = () => (
-
- Appeal crowdfunding
- The jury decision is appealed when two options are fully funded.
-
-
-
-);
+const Classic: React.FC = () => {
+ const [isPopupOpen, setIsPopupOpen] = useState(false);
+ const [amount, setAmount] = useState("");
+ const { selectedOption } = useSelectedOptionContext();
+ const options = useOptionsContext();
-export default Classic;
+ return (
+ <>
+ {isPopupOpen && (
+
+ )}
+ Appeal crowdfunding
+ The jury decision is appealed when two options are fully funded.
+
+
+ >
+ );
+};
+
+const ClassicWrapper: React.FC = () => {
+ return (
+
+
+
+ );
+};
+
+export default ClassicWrapper;
diff --git a/web/src/pages/Cases/CaseDetails/Appeal/index.tsx b/web/src/pages/Cases/CaseDetails/Appeal/index.tsx
index d81c37167..b30c6a24f 100644
--- a/web/src/pages/Cases/CaseDetails/Appeal/index.tsx
+++ b/web/src/pages/Cases/CaseDetails/Appeal/index.tsx
@@ -1,8 +1,8 @@
import React from "react";
-import Classic from "./Classic";
+import ClassicWrapper from "./Classic";
import { Periods } from "consts/periods";
const Appeal: React.FC<{ currentPeriodIndex: number }> = ({ currentPeriodIndex }) =>
- Periods.appeal === currentPeriodIndex ? : Not in appeal period ;
+ Periods.appeal === currentPeriodIndex ? : Not in appeal period ;
export default Appeal;
diff --git a/web/src/pages/Cases/CaseDetails/Voting/Classic.tsx b/web/src/pages/Cases/CaseDetails/Voting/Classic.tsx
index 3d44bab92..e4e878792 100644
--- a/web/src/pages/Cases/CaseDetails/Voting/Classic.tsx
+++ b/web/src/pages/Cases/CaseDetails/Voting/Classic.tsx
@@ -44,7 +44,13 @@ const RefuseToArbitrateContainer = styled.div`
justify-content: center;
`;
-const Classic: React.FC<{ arbitrable: `0x${string}`; voteIDs: string[] }> = ({ arbitrable, voteIDs }) => {
+interface IClassic {
+ arbitrable: `0x${string}`;
+ voteIDs: string[];
+ setIsOpen: (val: boolean) => void;
+}
+
+const Classic: React.FC = ({ arbitrable, voteIDs, setIsOpen }) => {
const { id } = useParams();
const parsedDisputeID = BigInt(id ?? 0);
const parsedVoteIDs = useMemo(() => voteIDs.map((voteID) => BigInt(voteID)), [voteIDs]);
@@ -70,10 +76,14 @@ const Classic: React.FC<{ arbitrable: `0x${string}`; voteIDs: string[] }> = ({ a
],
});
if (walletClient) {
- wrapWithToast(async () => await walletClient.writeContract(request), publicClient).finally(() => {
- setChosenOption(-1);
- setIsSending(false);
- });
+ wrapWithToast(async () => await walletClient.writeContract(request), publicClient)
+ .then(() => {
+ setIsOpen(true);
+ })
+ .finally(() => {
+ setChosenOption(-1);
+ setIsSending(false);
+ });
}
};
diff --git a/web/src/pages/Cases/CaseDetails/Voting/index.tsx b/web/src/pages/Cases/CaseDetails/Voting/index.tsx
index bbdaec0c9..d9c8ada77 100644
--- a/web/src/pages/Cases/CaseDetails/Voting/index.tsx
+++ b/web/src/pages/Cases/CaseDetails/Voting/index.tsx
@@ -1,13 +1,23 @@
-import React from "react";
+import React, { useState } from "react";
import { useParams } from "react-router-dom";
import { useAccount } from "wagmi";
+import { useLockBodyScroll } from "react-use";
import { useDisputeDetailsQuery } from "queries/useDisputeDetailsQuery";
import { useDrawQuery } from "queries/useDrawQuery";
import Classic from "./Classic";
import VotingHistory from "./VotingHistory";
+import Popup, { PopupType } from "components/Popup";
import { Periods } from "consts/periods";
import { isUndefined } from "utils/index";
+import { getPeriodEndTimestamp } from "components/DisputeCard";
import { useDisputeKitClassicIsVoteActive } from "hooks/contracts/generated";
+import VoteIcon from "assets/svgs/icons/voted.svg";
+
+function formatDate(unixTimestamp: number): string {
+ const date = new Date(unixTimestamp * 1000);
+ const options: Intl.DateTimeFormatOptions = { month: "long", day: "2-digit", year: "numeric" };
+ return date.toLocaleDateString("en-US", options);
+}
const Voting: React.FC<{
arbitrable?: `0x${string}`;
@@ -24,14 +34,37 @@ const Voting: React.FC<{
args: [BigInt(id ?? 0), roundId, voteId],
watch: true,
});
- return drawData &&
- !isUndefined(arbitrable) &&
- currentPeriodIndex === Periods.vote &&
- drawData.draws?.length > 0 &&
- !voted ? (
- draw.voteID)} />
- ) : (
-
+ const [isPopupOpen, setIsPopupOpen] = useState(false);
+ useLockBodyScroll(isPopupOpen);
+ const lastPeriodChange = disputeData?.dispute?.lastPeriodChange;
+ const timesPerPeriod = disputeData?.dispute?.court?.timesPerPeriod;
+ const finalDate =
+ !isUndefined(currentPeriodIndex) &&
+ !isUndefined(timesPerPeriod) &&
+ getPeriodEndTimestamp(lastPeriodChange, currentPeriodIndex, timesPerPeriod);
+
+ return (
+ <>
+ {isPopupOpen && (
+
+ )}
+ {drawData &&
+ !isUndefined(arbitrable) &&
+ currentPeriodIndex === Periods.vote &&
+ drawData.draws?.length > 0 &&
+ !voted ? (
+ draw.voteID)} />
+ ) : (
+
+ )}
+ >
);
};
diff --git a/web/src/pages/Courts/CourtDetails/StakePanel/InputDisplay.tsx b/web/src/pages/Courts/CourtDetails/StakePanel/InputDisplay.tsx
index 5451f9b91..b43e5aa7e 100644
--- a/web/src/pages/Courts/CourtDetails/StakePanel/InputDisplay.tsx
+++ b/web/src/pages/Courts/CourtDetails/StakePanel/InputDisplay.tsx
@@ -5,7 +5,6 @@ import { formatEther } from "viem";
import { useDebounce } from "react-use";
import { useAccount } from "wagmi";
import { Field } from "@kleros/ui-components-library";
-
import { useParsedAmount } from "hooks/useParsedAmount";
import { useKlerosCoreGetJurorBalance, usePnkBalanceOf } from "hooks/contracts/generated";
import StakeWithdrawButton, { ActionType } from "./StakeWithdrawButton";
@@ -38,10 +37,19 @@ interface IInputDisplay {
action: ActionType;
isSending: boolean;
setIsSending: (arg0: boolean) => void;
+ setIsPopupOpen: (arg0: boolean) => void;
+ amount: string;
+ setAmount: (arg0: string) => void;
}
-const InputDisplay: React.FC = ({ action, isSending, setIsSending }) => {
- const [amount, setAmount] = useState("");
+const InputDisplay: React.FC = ({
+ action,
+ isSending,
+ setIsSending,
+ setIsPopupOpen,
+ amount,
+ setAmount,
+}) => {
const [debouncedAmount, setDebouncedAmount] = useState("");
useDebounce(() => setDebouncedAmount(amount), 500, [amount]);
const parsedAmount = useParsedAmount(debouncedAmount);
@@ -96,6 +104,7 @@ const InputDisplay: React.FC = ({ action, isSending, setIsSending
setAmount,
isSending,
setIsSending,
+ setIsPopupOpen,
}}
/>
diff --git a/web/src/pages/Courts/CourtDetails/StakePanel/StakeWithdrawButton.tsx b/web/src/pages/Courts/CourtDetails/StakePanel/StakeWithdrawButton.tsx
index 000d1d5aa..962c44184 100644
--- a/web/src/pages/Courts/CourtDetails/StakePanel/StakeWithdrawButton.tsx
+++ b/web/src/pages/Courts/CourtDetails/StakePanel/StakeWithdrawButton.tsx
@@ -28,9 +28,17 @@ interface IActionButton {
action: ActionType;
setIsSending: (arg0: boolean) => void;
setAmount: (arg0: string) => void;
+ setIsPopupOpen: (arg0: boolean) => void;
}
-const StakeWithdrawButton: React.FC = ({ parsedAmount, action, setAmount, isSending, setIsSending }) => {
+const StakeWithdrawButton: React.FC = ({
+ parsedAmount,
+ action,
+ setAmount,
+ isSending,
+ setIsSending,
+ setIsPopupOpen,
+}) => {
const { id } = useParams();
const { address } = useAccount();
const klerosCore = getKlerosCore({});
@@ -91,11 +99,10 @@ const StakeWithdrawButton: React.FC = ({ parsedAmount, action, se
const handleStake = () => {
if (typeof setStake !== "undefined") {
setIsSending(true);
- wrapWithToast(async () => await setStake().then((response) => response.hash), publicClient)
- .then(() => {
- setAmount("");
- })
- .finally(() => setIsSending(false));
+ wrapWithToast(async () => await setStake().then((response) => response.hash), publicClient).finally(() => {
+ setIsSending(false);
+ setIsPopupOpen(true);
+ });
}
};
diff --git a/web/src/pages/Courts/CourtDetails/StakePanel/index.tsx b/web/src/pages/Courts/CourtDetails/StakePanel/index.tsx
index 640d2c146..d814e9e56 100644
--- a/web/src/pages/Courts/CourtDetails/StakePanel/index.tsx
+++ b/web/src/pages/Courts/CourtDetails/StakePanel/index.tsx
@@ -1,18 +1,22 @@
import React, { useState } from "react";
import styled from "styled-components";
-
+import { useLockBodyScroll } from "react-use";
import Tag from "components/Tag";
import JurorBalanceDisplay from "./JurorStakeDisplay";
import InputDisplay from "./InputDisplay";
import { ActionType } from "./StakeWithdrawButton";
+import Popup, { PopupType } from "components/Popup/index";
+import BalanceIcon from "assets/svgs/icons/balance.svg";
-const StakePanel: React.FC<{ courtName: string }> = ({
- courtName = "General Court",
-}) => {
+const StakePanel: React.FC<{ courtName: string; id: string }> = ({ courtName = "General Court", id }) => {
+ const [amount, setAmount] = useState("");
const [isSending, setIsSending] = useState(false);
+ const [isPopupOpen, setIsPopupOpen] = useState(false);
const [isActive, setIsActive] = useState(true);
const [action, setAction] = useState(ActionType.stake);
+ useLockBodyScroll(isPopupOpen);
+
const handleClick = (action: ActionType) => {
setIsActive(action === ActionType.stake);
setAction(action);
@@ -22,25 +26,30 @@ const StakePanel: React.FC<{ courtName: string }> = ({
return (
- handleClick(ActionType.stake)}
- />
- handleClick(ActionType.withdraw)}
- />
+ handleClick(ActionType.stake)} />
+ handleClick(ActionType.withdraw)} />
-
+
+ {isPopupOpen && (
+
+ )}
);
};
@@ -48,6 +57,7 @@ const StakePanel: React.FC<{ courtName: string }> = ({
export default StakePanel;
const Container = styled.div`
+ position: relative;
width: 100%;
margin-top: 32px;
display: flex;
diff --git a/web/src/pages/Courts/CourtDetails/index.tsx b/web/src/pages/Courts/CourtDetails/index.tsx
index a0fb7e591..82abe751d 100644
--- a/web/src/pages/Courts/CourtDetails/index.tsx
+++ b/web/src/pages/Courts/CourtDetails/index.tsx
@@ -5,7 +5,7 @@ import { useParams } from "react-router-dom";
import { Card, Breadcrumb } from "@kleros/ui-components-library";
import { useCourtPolicy } from "queries/useCourtPolicy";
import { useCourtTree, CourtTreeQuery } from "queries/useCourtTree";
-
+import { isUndefined } from "utils/index";
import Stats from "./Stats";
import Description from "./Description";
import StakePanel from "./StakePanel";
@@ -30,7 +30,7 @@ const CourtDetails: React.FC = () => {
-
+
@@ -60,11 +60,7 @@ interface IItem {
id: string;
}
-const getCourtsPath = (
- node: CourtTreeQuery["court"],
- id: string | undefined,
- path: IItem[] = []
-): IItem[] | null => {
+const getCourtsPath = (node: CourtTreeQuery["court"], id: string | undefined, path: IItem[] = []): IItem[] | null => {
if (!node || !id) return null;
if (node.id === id) {
diff --git a/web/src/pages/Dashboard/Courts/CourtCard.tsx b/web/src/pages/Dashboard/Courts/CourtCard.tsx
index 822d898a1..b7d385d57 100644
--- a/web/src/pages/Dashboard/Courts/CourtCard.tsx
+++ b/web/src/pages/Dashboard/Courts/CourtCard.tsx
@@ -33,7 +33,7 @@ const tooltipMsg =
"The locked stake of incoherent jurors is redistributed as incentives for " +
"the coherent jurors.";
-const format = (value: bigint | undefined): string => (value !== undefined ? formatEther(value) : "0");
+export const format = (value: bigint | undefined): string => (value !== undefined ? formatEther(value) : "0");
interface ICourtCard {
id: string;