Skip to content

Commit 8cde978

Browse files
authored
Merge pull request #1775 from kleros/feat/new-stake-flow
feat(web): new-stake-flow
2 parents 9b945af + 40fa153 commit 8cde978

21 files changed

+1046
-579
lines changed

package.json

+1-2
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,7 @@
2525
"web-devtools",
2626
"eslint-config",
2727
"prettier-config",
28-
"tsconfig",
29-
"kleros-app"
28+
"tsconfig"
3029
],
3130
"packageManager": "[email protected]",
3231
"volta": {

web/netlify.toml

-2
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,6 @@ NETLIFY_YARN_WORKSPACES = "true"
66
YARN_ENABLE_GLOBAL_CACHE = "true"
77
# YARN_CACHE_FOLDER = "$HOME/.yarn_cache"
88
# YARN_VERSION = "3.2.0"
9-
[build]
10-
command = "yarn workspace @kleros/kleros-v2-contracts install && yarn workspace @kleros/kleros-app install && yarn workspace @kleros/kleros-v2-web install && yarn workspace @kleros/kleros-v2-contracts build && yarn workspace @kleros/kleros-app build && yarn workspace @kleros/kleros-v2-web build-netlify"
119

1210
[functions]
1311
directory = "web/netlify/functions/"

web/package.json

+5-5
Original file line numberDiff line numberDiff line change
@@ -78,15 +78,15 @@
7878
},
7979
"dependencies": {
8080
"@cyntler/react-doc-viewer": "^1.17.0",
81-
"@kleros/kleros-app": "workspace:^",
81+
"@kleros/kleros-app": "^2.0.1",
8282
"@kleros/kleros-sdk": "workspace:^",
8383
"@kleros/kleros-v2-contracts": "workspace:^",
84-
"@kleros/ui-components-library": "^2.15.0",
84+
"@kleros/ui-components-library": "^2.16.0",
8585
"@lifi/wallet-management": "^3.4.5",
8686
"@lifi/widget": "^3.12.2",
8787
"@sentry/react": "^7.120.0",
8888
"@sentry/tracing": "^7.120.0",
89-
"@tanstack/react-query": "^5.61.0",
89+
"@tanstack/react-query": "^5.56.2",
9090
"@types/react-modal": "^3.16.3",
9191
"@wagmi/connectors": "^5.5.0",
9292
"@wagmi/core": "^2.15.0",
@@ -117,7 +117,7 @@
117117
"react-toastify": "^9.1.3",
118118
"react-use": "^17.5.1",
119119
"styled-components": "^5.3.3",
120-
"viem": "^2.21.48",
121-
"wagmi": "^2.13.0"
120+
"viem": "^2.21.54",
121+
"wagmi": "^2.13.5"
122122
}
123123
}
+1-1
Loading

web/src/components/ExternalLink.tsx

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
import { Link } from "react-router-dom";
21
import styled from "styled-components";
32

3+
import { Link } from "react-router-dom";
4+
45
export const ExternalLink = styled(Link)`
56
:hover {
67
text-decoration: underline;

web/src/components/Spinner.tsx

+27
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import styled, { keyframes } from "styled-components";
2+
3+
import SpinnerIcon from "svgs/icons/spinner.svg";
4+
5+
const rotating = keyframes`
6+
0%{
7+
transform: rotate(0deg);
8+
}
9+
50%{
10+
transform: rotate(180deg);
11+
}
12+
100%{
13+
transform: rotate(360deg);
14+
}
15+
`;
16+
17+
const Spinner = styled(SpinnerIcon)`
18+
path {
19+
fill: ${({ theme }) => theme.primaryBlue};
20+
}
21+
width: 16px;
22+
height: 16px;
23+
margin-right: 4px;
24+
animation: ${rotating} 2s ease-in-out infinite normal;
25+
`;
26+
27+
export default Spinner;

web/src/components/TxnHash.tsx

+40
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import React, { useMemo } from "react";
2+
import styled from "styled-components";
3+
4+
import NewTabIcon from "svgs/icons/new-tab.svg";
5+
6+
import { DEFAULT_CHAIN, getChain } from "consts/chains";
7+
8+
import { ExternalLink } from "./ExternalLink";
9+
10+
const TxnLabel = styled.label<{ variant: string }>`
11+
display: flex;
12+
gap: 4px;
13+
color: ${({ theme, variant }) => (variant === "pending" ? theme.primaryBlue : theme[variant])};
14+
cursor: pointer;
15+
path {
16+
fill: ${({ theme, variant }) => (variant === "pending" ? theme.primaryBlue : theme[variant])};
17+
}
18+
`;
19+
20+
interface ITxnHash {
21+
hash: `0x${string}`;
22+
variant: "success" | "error" | "pending";
23+
}
24+
const TxnHash: React.FC<ITxnHash> = ({ hash, variant }) => {
25+
const transactionExplorerLink = useMemo(() => {
26+
return `${getChain(DEFAULT_CHAIN)?.blockExplorers?.default.url}/tx/${hash}`;
27+
}, [hash]);
28+
29+
return (
30+
<ExternalLink to={transactionExplorerLink} rel="noopener noreferrer" target="_blank">
31+
<TxnLabel {...{ variant }}>
32+
{" "}
33+
<span>{hash.substring(0, 6) + "..." + hash.substring(hash.length - 4)}</span>
34+
<NewTabIcon />
35+
</TxnLabel>
36+
</ExternalLink>
37+
);
38+
};
39+
40+
export default TxnHash;

web/src/hooks/queries/useCourtDetails.ts

+1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ const courtDetailsQuery = graphql(`
2323
paidPNK
2424
timesPerPeriod
2525
feeForJuror
26+
name
2627
}
2728
}
2829
`);

web/src/hooks/usePNKData.tsx

+46
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import { useAccount } from "wagmi";
2+
3+
import { DEFAULT_CHAIN } from "consts/chains";
4+
5+
import { REFETCH_INTERVAL } from "src/consts";
6+
import { isUndefined } from "src/utils";
7+
8+
import {
9+
klerosCoreAddress,
10+
useReadPnkAllowance,
11+
useReadPnkBalanceOf,
12+
useReadSortitionModuleGetJurorBalance,
13+
} from "./contracts/generated";
14+
15+
interface UsePnkDataParams {
16+
courtId?: string;
17+
}
18+
19+
/**
20+
* @description hook to provide user's pnk data. (pnk balance, pnk allowance, jurorBalance for provided courtId)
21+
* @param param0 optional court Id to fetch juror balance for. Defaults to 0
22+
*/
23+
export const usePnkData = ({ courtId = "0" }: UsePnkDataParams) => {
24+
const { address } = useAccount();
25+
const queryConfig = {
26+
enabled: !isUndefined(address),
27+
refetchInterval: REFETCH_INTERVAL,
28+
};
29+
30+
const { data: balance } = useReadPnkBalanceOf({
31+
query: queryConfig,
32+
args: [address!],
33+
});
34+
35+
const { data: jurorBalance } = useReadSortitionModuleGetJurorBalance({
36+
query: queryConfig,
37+
args: [address ?? "0x", BigInt(courtId)],
38+
});
39+
40+
const { data: allowance, refetch: refetchAllowance } = useReadPnkAllowance({
41+
query: queryConfig,
42+
args: [address ?? "0x", klerosCoreAddress[DEFAULT_CHAIN]],
43+
});
44+
45+
return { balance, jurorBalance, allowance, refetchAllowance };
46+
};

web/src/pages/Courts/CourtDetails/StakePanel/InputDisplay.tsx

+13-35
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,26 @@
11
import React, { useState, useMemo, useEffect } from "react";
22
import styled, { css } from "styled-components";
3-
import { landscapeStyle } from "styles/landscapeStyle";
43

54
import { useParams } from "react-router-dom";
65
import { useDebounce } from "react-use";
7-
import { useAccount } from "wagmi";
8-
9-
import { REFETCH_INTERVAL } from "consts/index";
106

11-
import { useReadSortitionModuleGetJurorBalance, useReadPnkBalanceOf } from "hooks/contracts/generated";
127
import { useParsedAmount } from "hooks/useParsedAmount";
13-
8+
import { usePnkData } from "hooks/usePNKData";
149
import { commify, uncommify } from "utils/commify";
1510
import { formatPNK, roundNumberDown } from "utils/format";
1611
import { isUndefined } from "utils/index";
1712

13+
import { landscapeStyle } from "styles/landscapeStyle";
14+
1815
import { NumberInputField } from "components/NumberInputField";
16+
1917
import StakeWithdrawButton, { ActionType } from "./StakeWithdrawButton";
2018

2119
const StyledField = styled(NumberInputField)`
2220
height: fit-content;
21+
input {
22+
border-radius: 3px 0px 0px 3px;
23+
}
2324
`;
2425

2526
const LabelArea = styled.div`
@@ -62,48 +63,27 @@ const EnsureChainContainer = styled.div`
6263
button {
6364
height: 45px;
6465
border: 1px solid ${({ theme }) => theme.stroke};
66+
border-radius: 0px 3px 3px 0px;
6567
}
6668
`;
6769

6870
interface IInputDisplay {
6971
action: ActionType;
70-
isSending: boolean;
71-
setIsSending: (arg0: boolean) => void;
72-
setIsPopupOpen: (arg0: boolean) => void;
7372
amount: string;
7473
setAmount: (arg0: string) => void;
7574
}
7675

77-
const InputDisplay: React.FC<IInputDisplay> = ({
78-
action,
79-
isSending,
80-
setIsSending,
81-
setIsPopupOpen,
82-
amount,
83-
setAmount,
84-
}) => {
76+
const InputDisplay: React.FC<IInputDisplay> = ({ action, amount, setAmount }) => {
8577
const [debouncedAmount, setDebouncedAmount] = useState("");
8678
const [errorMsg, setErrorMsg] = useState<string | undefined>();
8779
useDebounce(() => setDebouncedAmount(amount), 500, [amount]);
8880
const parsedAmount = useParsedAmount(uncommify(debouncedAmount) as `${number}`);
8981

9082
const { id } = useParams();
91-
const { address } = useAccount();
92-
const { data: balance } = useReadPnkBalanceOf({
93-
query: {
94-
enabled: !isUndefined(address),
95-
refetchInterval: REFETCH_INTERVAL,
96-
},
97-
args: [address ?? "0x"],
98-
});
83+
const { balance, jurorBalance } = usePnkData({ courtId: id });
84+
9985
const parsedBalance = formatPNK(balance ?? 0n, 0, true);
100-
const { data: jurorBalance } = useReadSortitionModuleGetJurorBalance({
101-
query: {
102-
enabled: !isUndefined(address),
103-
refetchInterval: REFETCH_INTERVAL,
104-
},
105-
args: [address ?? "0x", BigInt(id ?? "0")],
106-
});
86+
10787
const parsedStake = formatPNK(jurorBalance?.[2] ?? 0n, 0, true);
10888
const isStaking = useMemo(() => action === ActionType.stake, [action]);
10989

@@ -147,12 +127,10 @@ const InputDisplay: React.FC<IInputDisplay> = ({
147127
<EnsureChainContainer>
148128
<StakeWithdrawButton
149129
{...{
130+
amount,
150131
parsedAmount,
151132
action,
152133
setAmount,
153-
isSending,
154-
setIsSending,
155-
setIsPopupOpen,
156134
setErrorMsg,
157135
}}
158136
/>

web/src/pages/Courts/CourtDetails/StakePanel/Simulator/QuantityToSimulate.tsx

+5-1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import React from "react";
22
import styled from "styled-components";
3+
34
import Skeleton from "react-loading-skeleton";
45

56
import { commify } from "utils/commify";
@@ -13,6 +14,7 @@ const Container = styled.div`
1314
align-items: center;
1415
flex-wrap: wrap;
1516
gap: 0 8px;
17+
justify-content: center;
1618
`;
1719

1820
const TextWithTooltipContainer = styled.div`
@@ -48,13 +50,15 @@ interface IQuantityToSimulate {
4850
jurorCurrentSpecificStake: number | undefined;
4951
isStaking: boolean;
5052
amountToStake: number;
53+
className?: string;
5154
}
5255

5356
const QuantityToSimulate: React.FC<IQuantityToSimulate> = ({
5457
isStaking,
5558
jurorCurrentEffectiveStake,
5659
jurorCurrentSpecificStake,
5760
amountToStake,
61+
className,
5862
}) => {
5963
const effectiveStakeDisplay = !isUndefined(jurorCurrentEffectiveStake) ? (
6064
`${commify(jurorCurrentEffectiveStake)} PNK`
@@ -85,7 +89,7 @@ const QuantityToSimulate: React.FC<IQuantityToSimulate> = ({
8589
);
8690

8791
return (
88-
<Container>
92+
<Container {...{ className }}>
8993
<Quantity>{effectiveStakeDisplay}</Quantity>
9094
<TextWithTooltipContainer>
9195
<WithHelpTooltip

0 commit comments

Comments
 (0)