Skip to content

Commit 2a1f4d0

Browse files
authoredDec 19, 2024··
Merge branch 'dev' into feat/ui-improvements
2 parents d93e2ff + f52ed9c commit 2a1f4d0

File tree

10 files changed

+557
-38
lines changed

10 files changed

+557
-38
lines changed
 

‎kleros-app/src/lib/atlas/hooks/useSessionStorage.ts

+3
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ export function useSessionStorage<T>(keyName: string, defaultValue: T) {
77

88
return value ? JSON.parse(value) : defaultValue;
99
} catch (err) {
10+
// eslint-disable-next-line no-console
11+
console.log("useSessionStorage:", { err });
12+
1013
return defaultValue;
1114
}
1215
});

‎kleros-app/src/lib/atlas/providers/AtlasProvider.tsx

+33-2
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import {
2525
import { GraphQLError } from "graphql";
2626
import { isUndefined } from "../../../utils";
2727
import { useSessionStorage } from "../hooks/useSessionStorage";
28+
import { fetchRestrictions, Role } from "../utils/fetchRestrictions";
2829

2930
interface IAtlasProvider {
3031
isVerified: boolean;
@@ -44,6 +45,7 @@ interface IAtlasProvider {
4445
isError: boolean;
4546
}
4647
>;
48+
roleRestrictions: Role[] | undefined;
4749
}
4850

4951
const Context = createContext<IAtlasProvider | undefined>(undefined);
@@ -73,7 +75,7 @@ export const AtlasProvider: React.FC<{ config: AtlasConfig; children?: React.Rea
7375
}
7476
: undefined;
7577
return new GraphQLClient(`${config.uri}/graphql`, { headers });
76-
}, [authToken]);
78+
}, [authToken, config.uri]);
7779

7880
/**
7981
* @description verifies user authorisation
@@ -142,6 +144,22 @@ export const AtlasProvider: React.FC<{ config: AtlasConfig; children?: React.Rea
142144
queryClient
143145
);
144146

147+
const { data: roleRestrictions } = useQuery(
148+
{
149+
queryKey: [`RoleRestrictions`],
150+
enabled: Boolean(config.product),
151+
staleTime: Infinity,
152+
queryFn: async () => {
153+
try {
154+
return await fetchRestrictions(atlasGqlClient, config.product);
155+
} catch {
156+
return undefined;
157+
}
158+
},
159+
},
160+
queryClient
161+
);
162+
145163
useEffect(() => {
146164
if (!isVerified) return;
147165
refetchUser();
@@ -255,6 +273,17 @@ export const AtlasProvider: React.FC<{ config: AtlasConfig; children?: React.Rea
255273
async (file: File, role: Roles) => {
256274
try {
257275
if (!address || !isVerified || !config.uri || !authToken) return null;
276+
277+
if (roleRestrictions) {
278+
const restrictions = roleRestrictions.find((supportedRoles) => Roles[supportedRoles.name] === role);
279+
280+
if (!restrictions) throw new Error("Unsupported role.");
281+
if (!restrictions.restriction.allowedMimeTypes.includes(file.type)) throw new Error("Unsupported file type.");
282+
if (file.size > restrictions.restriction.maxSize)
283+
throw new Error(
284+
`File too big. Max allowed size : ${(restrictions.restriction.maxSize / (1024 * 1024)).toFixed(2)} mb.`
285+
);
286+
}
258287
setIsUploadingFile(true);
259288

260289
const hash = await fetchWithAuthErrorHandling(() =>
@@ -267,7 +296,7 @@ export const AtlasProvider: React.FC<{ config: AtlasConfig; children?: React.Rea
267296
setIsUploadingFile(false);
268297
}
269298
},
270-
[address, isVerified, setIsUploadingFile, authToken, config.uri, config.product]
299+
[address, isVerified, setIsUploadingFile, authToken, config.uri, config.product, roleRestrictions]
271300
);
272301

273302
/**
@@ -309,6 +338,7 @@ export const AtlasProvider: React.FC<{ config: AtlasConfig; children?: React.Rea
309338
isUploadingFile,
310339
uploadFile,
311340
confirmEmail,
341+
roleRestrictions,
312342
}),
313343
[
314344
isVerified,
@@ -324,6 +354,7 @@ export const AtlasProvider: React.FC<{ config: AtlasConfig; children?: React.Rea
324354
isUploadingFile,
325355
uploadFile,
326356
confirmEmail,
357+
roleRestrictions,
327358
]
328359
)}
329360
>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import { gql, type GraphQLClient } from "graphql-request";
2+
import { Products } from ".";
3+
4+
export type Role = {
5+
name: string;
6+
restriction: {
7+
maxSize: number;
8+
allowedMimeTypes: string[];
9+
};
10+
};
11+
12+
type FetchRolesResponse = {
13+
roles: Role[];
14+
};
15+
16+
const query = gql`
17+
query Roles($product: Products!) {
18+
roles(product: $product) {
19+
name
20+
restriction {
21+
maxSize
22+
allowedMimeTypes
23+
}
24+
}
25+
}
26+
`;
27+
28+
export async function fetchRestrictions(client: GraphQLClient, product: Products): Promise<Role[]> {
29+
return client
30+
.request<FetchRolesResponse>(query, { product })
31+
.then((response) => response.roles)
32+
.catch((errors) => {
33+
// eslint-disable-next-line no-console
34+
console.log("Error fetching roles :", { errors });
35+
const errorMessage = Array.isArray(errors?.response?.errors)
36+
? errors.response.errors[0]?.message
37+
: "Error fetching roles";
38+
throw Error(errorMessage);
39+
});
40+
}

‎package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,8 @@
2525
"web-devtools",
2626
"eslint-config",
2727
"prettier-config",
28-
"tsconfig"
28+
"tsconfig",
29+
"kleros-app"
2930
],
3031
"packageManager": "yarn@4.5.1",
3132
"volta": {

‎web/src/components/FileViewer/index.tsx

+4
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,10 @@ const Wrapper = styled.div`
2020

2121
const StyledDocViewer = styled(DocViewer)`
2222
background-color: ${({ theme }) => theme.whiteBackground} !important;
23+
24+
#pdf-controls {
25+
z-index: 3;
26+
}
2327
`;
2428

2529
/**

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

+14-3
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
import React, { useState, useMemo, useEffect } from "react";
22
import styled from "styled-components";
33

4-
import { hoverShortTransitionTiming } from "styles/commonStyles";
5-
64
import { useParams } from "react-router-dom";
75
import { useDebounce } from "react-use";
86

@@ -12,6 +10,10 @@ import { commify, uncommify } from "utils/commify";
1210
import { formatPNK, roundNumberDown } from "utils/format";
1311
import { isUndefined } from "utils/index";
1412

13+
import { useCourtDetails } from "queries/useCourtDetails";
14+
15+
import { hoverShortTransitionTiming } from "styles/commonStyles";
16+
1517
import { NumberInputField } from "components/NumberInputField";
1618

1719
import StakeWithdrawButton, { ActionType } from "./StakeWithdrawButton";
@@ -75,6 +77,7 @@ const InputDisplay: React.FC<IInputDisplay> = ({ action, amount, setAmount }) =>
7577

7678
const { id } = useParams();
7779
const { balance, jurorBalance } = usePnkData({ courtId: id });
80+
const { data: courtDetails } = useCourtDetails(id);
7881

7982
const parsedBalance = formatPNK(balance ?? 0n, 0, true);
8083

@@ -88,10 +91,18 @@ const InputDisplay: React.FC<IInputDisplay> = ({ action, amount, setAmount }) =>
8891
setErrorMsg("Insufficient balance to stake this amount");
8992
} else if (!isStaking && jurorBalance && parsedAmount > jurorBalance[2]) {
9093
setErrorMsg("Insufficient staked amount to withdraw this amount");
94+
} else if (
95+
action === ActionType.stake &&
96+
courtDetails &&
97+
jurorBalance &&
98+
parsedAmount !== 0n &&
99+
jurorBalance[2] + parsedAmount < BigInt(courtDetails?.court?.minStake)
100+
) {
101+
setErrorMsg(`Min Stake in court is: ${formatPNK(courtDetails?.court?.minStake)} PNK`);
91102
} else {
92103
setErrorMsg(undefined);
93104
}
94-
}, [parsedAmount, isStaking, balance, jurorBalance]);
105+
}, [parsedAmount, isStaking, balance, jurorBalance, action, courtDetails]);
95106

96107
return (
97108
<>

‎web/src/pages/Courts/CourtDetails/StakePanel/StakeWithdrawButton.tsx

+26-14
Original file line numberDiff line numberDiff line change
@@ -15,14 +15,14 @@ import {
1515
useSimulatePnkIncreaseAllowance,
1616
useWritePnkIncreaseAllowance,
1717
} from "hooks/contracts/generated";
18-
import { useCourtDetails } from "hooks/queries/useCourtDetails";
1918
import { useLockOverlayScroll } from "hooks/useLockOverlayScroll";
2019
import { usePnkData } from "hooks/usePNKData";
21-
import { formatETH } from "utils/format";
2220
import { isUndefined } from "utils/index";
2321
import { parseWagmiError } from "utils/parseWagmiError";
2422
import { refetchWithRetry } from "utils/refecthWithRetry";
2523

24+
import { useCourtDetails } from "queries/useCourtDetails";
25+
2626
import { EnsureChain } from "components/EnsureChain";
2727

2828
import StakeWithdrawPopup from "./StakeWithdrawPopup";
@@ -67,9 +67,8 @@ const StakeWithdrawButton: React.FC<IActionButton> = ({
6767
const controllerRef = useRef<AbortController | null>(null);
6868
useLockOverlayScroll(isPopupOpen);
6969

70-
const { data: courtDetails } = useCourtDetails(id);
7170
const { balance, jurorBalance, allowance, refetchAllowance } = usePnkData({ courtId: id });
72-
71+
const { data: courtDetails } = useCourtDetails(id);
7372
const publicClient = usePublicClient();
7473

7574
const isStaking = action === ActionType.stake;
@@ -181,6 +180,16 @@ const StakeWithdrawButton: React.FC<IActionButton> = ({
181180
)
182181
);
183182
});
183+
} else {
184+
updatePopupState(
185+
signal,
186+
getStakeSteps(
187+
StakeSteps.StakeFailed,
188+
...commonArgs,
189+
undefined,
190+
new Error("Simulation Failed. Please restart the process.")
191+
)
192+
);
184193
}
185194
},
186195
[setStake, setStakeConfig, publicClient, amount, theme, action]
@@ -248,20 +257,20 @@ const StakeWithdrawButton: React.FC<IActionButton> = ({
248257

249258
useEffect(() => {
250259
if (isPopupOpen) return;
251-
if (
252-
action === ActionType.stake &&
253-
targetStake !== 0n &&
254-
courtDetails &&
255-
targetStake < BigInt(courtDetails.court?.minStake)
256-
) {
257-
setErrorMsg(`Min Stake in court is: ${formatETH(courtDetails?.court?.minStake)}`);
258-
} else if (setStakeError || allowanceError) {
260+
if (setStakeError || allowanceError) {
259261
setErrorMsg(parseWagmiError(setStakeError || allowanceError));
260262
}
261-
}, [setStakeError, setErrorMsg, targetStake, courtDetails, allowanceError, isPopupOpen, action]);
263+
}, [setStakeError, setErrorMsg, targetStake, allowanceError, isPopupOpen]);
262264

263265
const isDisabled = useMemo(() => {
264-
if (parsedAmount == 0n) return true;
266+
if (
267+
parsedAmount == 0n ||
268+
(action === ActionType.stake &&
269+
targetStake !== 0n &&
270+
courtDetails &&
271+
targetStake < BigInt(courtDetails?.court?.minStake))
272+
)
273+
return true;
265274
if (isAllowance) {
266275
return isUndefined(increaseAllowanceConfig) || isSimulatingAllowance || !isUndefined(allowanceError);
267276
}
@@ -275,6 +284,9 @@ const StakeWithdrawButton: React.FC<IActionButton> = ({
275284
setStakeError,
276285
allowanceError,
277286
isAllowance,
287+
targetStake,
288+
action,
289+
courtDetails,
278290
]);
279291

280292
const closePopup = () => {

‎web/src/pages/Home/CourtOverview/Header.tsx

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
import React from "react";
22
import styled from "styled-components";
33

4-
import { responsiveSize } from "styles/responsiveSize";
5-
64
import { Button } from "@kleros/ui-components-library";
75

86
import Bookmark from "svgs/icons/bookmark.svg";
97

8+
import { responsiveSize } from "styles/responsiveSize";
9+
1010
import { InternalLink } from "components/InternalLink";
1111

1212
const StyledHeader = styled.div`

‎yarn.lock

+432-15
Large diffs are not rendered by default.

0 commit comments

Comments
 (0)
Please sign in to comment.