Skip to content

Commit 6d09c7d

Browse files
authored
Merge pull request #1819 from kleros/feat(web)/public-dashboards
feat: public dashboards
2 parents 50bcfb9 + 8b11b62 commit 6d09c7d

File tree

11 files changed

+127
-76
lines changed

11 files changed

+127
-76
lines changed

web/src/components/CasesDisplay/Filters.tsx

+4-3
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import styled, { css, useTheme } from "styled-components";
33

44
import { hoverShortTransitionTiming } from "styles/commonStyles";
55

6-
import { useNavigate, useParams } from "react-router-dom";
6+
import { useNavigate, useParams, useSearchParams } from "react-router-dom";
77

88
import { DropdownSelect } from "@kleros/ui-components-library";
99

@@ -55,16 +55,17 @@ const Filters: React.FC = () => {
5555
const { ruled, period, ...filterObject } = decodeURIFilter(filter ?? "all");
5656
const navigate = useNavigate();
5757
const location = useRootPath();
58+
const [searchParams] = useSearchParams();
5859

5960
const handleStatusChange = (value: string | number) => {
6061
const parsedValue = JSON.parse(value as string);
6162
const encodedFilter = encodeURIFilter({ ...filterObject, ...parsedValue });
62-
navigate(`${location}/1/${order}/${encodedFilter}`);
63+
navigate(`${location}/1/${order}/${encodedFilter}?${searchParams.toString()}`);
6364
};
6465

6566
const handleOrderChange = (value: string | number) => {
6667
const encodedFilter = encodeURIFilter({ ruled, period, ...filterObject });
67-
navigate(`${location}/1/${value}/${encodedFilter}`);
68+
navigate(`${location}/1/${value}/${encodedFilter}?${searchParams.toString()}`);
6869
};
6970

7071
const { isList, setIsList } = useIsList();

web/src/components/CasesDisplay/Search.tsx

+14-5
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
1-
import React, { useMemo, useState } from "react";
1+
import React, { useMemo, useRef, useState } from "react";
22
import styled, { css } from "styled-components";
33

44
import Skeleton from "react-loading-skeleton";
5-
import { useNavigate, useParams } from "react-router-dom";
5+
import { useNavigate, useParams, useSearchParams } from "react-router-dom";
66
import { useDebounce } from "react-use";
7-
87
import { Searchbar, DropdownCascader } from "@kleros/ui-components-library";
98

109
import { isEmpty, isUndefined } from "utils/index";
@@ -39,6 +38,7 @@ const SearchBarContainer = styled.div`
3938
const StyledSearchbar = styled(Searchbar)`
4039
flex: 1;
4140
flex-basis: 310px;
41+
4242
input {
4343
font-size: 16px;
4444
height: 45px;
@@ -53,16 +53,25 @@ const Search: React.FC = () => {
5353
const decodedFilter = decodeURIFilter(filter ?? "all");
5454
const { id: searchValue, ...filterObject } = decodedFilter;
5555
const [search, setSearch] = useState(searchValue ?? "");
56+
const initialRenderRef = useRef(true);
5657
const navigate = useNavigate();
58+
const [searchParams] = useSearchParams();
59+
5760
useDebounce(
5861
() => {
62+
if (initialRenderRef.current && isEmpty(search)) {
63+
initialRenderRef.current = false;
64+
return;
65+
}
66+
initialRenderRef.current = false;
5967
const newFilters = isEmpty(search) ? { ...filterObject } : { ...filterObject, id: search };
6068
const encodedFilter = encodeURIFilter(newFilters);
61-
navigate(`${location}/${page}/${order}/${encodedFilter}`);
69+
navigate(`${location}/${page}/${order}/${encodedFilter}?${searchParams.toString()}`);
6270
},
6371
500,
6472
[search]
6573
);
74+
6675
const { data: courtTreeData } = useCourtTree();
6776
const items = useMemo(() => {
6877
if (!isUndefined(courtTreeData)) {
@@ -82,7 +91,7 @@ const Search: React.FC = () => {
8291
onSelect={(value) => {
8392
const { court: _, ...filterWithoutCourt } = decodedFilter;
8493
const newFilter = value === "all" ? filterWithoutCourt : { ...decodedFilter, court: value.toString() };
85-
navigate(`${location}/${page}/${order}/${encodeURIFilter(newFilter)}`);
94+
navigate(`${location}/${page}/${order}/${encodeURIFilter(newFilter)}?${searchParams.toString()}`);
8695
}}
8796
/>
8897
) : (

web/src/components/EvidenceCard.tsx

+9-8
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ const AccountContainer = styled.div`
139139
}
140140
`;
141141

142-
const HoverStyle = css`
142+
const ExternalLinkHoverStyle = css`
143143
:hover {
144144
text-decoration: underline;
145145
color: ${({ theme }) => theme.primaryBlue};
@@ -155,12 +155,15 @@ const HoverStyle = css`
155155
`;
156156

157157
const Address = styled.p`
158-
${HoverStyle}
159158
margin: 0;
159+
160+
:hover {
161+
color: ${({ theme }) => theme.secondaryBlue};
162+
}
160163
`;
161164

162165
const StyledExternalLink = styled(ExternalLink)`
163-
${HoverStyle}
166+
${ExternalLinkHoverStyle}
164167
`;
165168

166169
const DesktopText = styled.span`
@@ -221,9 +224,7 @@ const EvidenceCard: React.FC<IEvidenceCard> = ({
221224
description,
222225
fileURI,
223226
}) => {
224-
const addressExplorerLink = useMemo(() => {
225-
return `${getChain(DEFAULT_CHAIN)?.blockExplorers?.default.url}/address/${sender}`;
226-
}, [sender]);
227+
const dashboardLink = `/dashboard/1/desc/all?address=${sender}`;
227228

228229
const transactionExplorerLink = useMemo(() => {
229230
return getTxnExplorerLink(transactionHash ?? "");
@@ -248,9 +249,9 @@ const EvidenceCard: React.FC<IEvidenceCard> = ({
248249
<BottomLeftContent>
249250
<AccountContainer>
250251
<Identicon size="24" string={sender} />
251-
<ExternalLink to={addressExplorerLink} rel="noopener noreferrer" target="_blank">
252+
<InternalLink to={dashboardLink}>
252253
<Address>{shortenAddress(sender)}</Address>
253-
</ExternalLink>
254+
</InternalLink>
254255
</AccountContainer>
255256
<StyledExternalLink to={transactionExplorerLink} rel="noopener noreferrer" target="_blank">
256257
<label>{formatDate(Number(timestamp), true)}</label>

web/src/pages/Cases/CaseDetails/Voting/VotesDetails/AccordionTitle.tsx

+9-11
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,16 @@
1-
import React, { useMemo } from "react";
1+
import React from "react";
22
import styled, { css } from "styled-components";
33

4+
import { landscapeStyle } from "styles/landscapeStyle";
5+
46
import Identicon from "react-identicons";
57

68
import { Answer } from "context/NewDisputeContext";
7-
import { DEFAULT_CHAIN, getChain } from "consts/chains";
89
import { getVoteChoice } from "utils/getVoteChoice";
910
import { isUndefined } from "utils/index";
1011
import { shortenAddress } from "utils/shortenAddress";
1112

12-
import { landscapeStyle } from "styles/landscapeStyle";
13+
import { InternalLink } from "components/InternalLink";
1314

1415
const TitleContainer = styled.div`
1516
display: flex;
@@ -41,12 +42,11 @@ const StyledSmall = styled.small`
4142
font-size: 16px;
4243
`;
4344

44-
const StyledA = styled.a`
45+
const StyledInternalLink = styled(InternalLink)`
4546
:hover {
46-
text-decoration: underline;
4747
label {
4848
cursor: pointer;
49-
color: ${({ theme }) => theme.primaryBlue};
49+
color: ${({ theme }) => theme.secondaryBlue};
5050
}
5151
}
5252
`;
@@ -88,17 +88,15 @@ const AccordionTitle: React.FC<{
8888
commited: boolean;
8989
hiddenVotes: boolean;
9090
}> = ({ juror, choice, voteCount, period, answers, isActiveRound, commited, hiddenVotes }) => {
91-
const addressExplorerLink = useMemo(() => {
92-
return `${getChain(DEFAULT_CHAIN)?.blockExplorers?.default.url}/address/${juror}`;
93-
}, [juror]);
91+
const dashboardLink = `/dashboard/1/desc/all?address=${juror}`;
9492

9593
return (
9694
<TitleContainer>
9795
<AddressContainer>
9896
<Identicon size="20" string={juror} />
99-
<StyledA href={addressExplorerLink} rel="noopener noreferrer" target="_blank">
97+
<StyledInternalLink to={dashboardLink}>
10098
<StyledLabel variant="secondaryText">{shortenAddress(juror)}</StyledLabel>
101-
</StyledA>
99+
</StyledInternalLink>
102100
</AddressContainer>
103101
<VoteStatus {...{ choice, period, answers, isActiveRound, commited, hiddenVotes }} />
104102
<StyledLabel variant="secondaryPurple">

web/src/pages/Dashboard/Courts/Header.tsx

+4-1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import React from "react";
22
import styled, { css } from "styled-components";
33

44
import { formatUnits } from "viem";
5+
import { useSearchParams } from "react-router-dom";
56

67
import LockerIcon from "svgs/icons/locker.svg";
78

@@ -57,10 +58,12 @@ interface IHeader {
5758

5859
const Header: React.FC<IHeader> = ({ lockedStake }) => {
5960
const formattedLockedStake = !isUndefined(lockedStake) && formatUnits(lockedStake, 18);
61+
const [searchParams] = useSearchParams();
62+
const searchParamAddress = searchParams.get("address")?.toLowerCase();
6063

6164
return (
6265
<Container>
63-
<StyledTitle>My Courts</StyledTitle>
66+
<StyledTitle>{searchParamAddress ? "Their" : "My"} Courts</StyledTitle>
6467
{!isUndefined(lockedStake) ? (
6568
<LockedPnk>
6669
<StyledLockerIcon />

web/src/pages/Dashboard/Courts/index.tsx

+13-6
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import React from "react";
22
import styled, { css } from "styled-components";
33

44
import Skeleton from "react-loading-skeleton";
5-
import { useAccount } from "wagmi";
5+
import { useSearchParams } from "react-router-dom";
66

77
import { useReadSortitionModuleGetJurorBalance } from "hooks/contracts/generated";
88

@@ -35,12 +35,17 @@ const StyledLabel = styled.label`
3535
font-size: ${responsiveSize(14, 16)};
3636
`;
3737

38-
const Courts: React.FC = () => {
39-
const { address } = useAccount();
40-
const { data: stakeData, isLoading } = useJurorStakeDetailsQuery(address?.toLowerCase() as `0x${string}`);
38+
interface ICourts {
39+
addressToQuery: `0x${string}`;
40+
}
41+
42+
const Courts: React.FC<ICourts> = ({ addressToQuery }) => {
43+
const { data: stakeData, isLoading } = useJurorStakeDetailsQuery(addressToQuery);
4144
const { data: jurorBalance } = useReadSortitionModuleGetJurorBalance({
42-
args: [address as `0x${string}`, BigInt(1)],
45+
args: [addressToQuery, BigInt(1)],
4346
});
47+
const [searchParams] = useSearchParams();
48+
const searchParamAddress = searchParams.get("address")?.toLowerCase();
4449
const stakedCourts = stakeData?.jurorTokensPerCourts?.filter(({ staked }) => staked > 0);
4550
const isStaked = stakedCourts && stakedCourts.length > 0;
4651
const lockedStake = jurorBalance?.[1];
@@ -49,7 +54,9 @@ const Courts: React.FC = () => {
4954
<Container>
5055
<Header lockedStake={lockedStake ?? BigInt(0)} />
5156
{isLoading ? <Skeleton /> : null}
52-
{!isStaked && !isLoading ? <StyledLabel>You are not staked in any court</StyledLabel> : null}
57+
{!isStaked && !isLoading ? (
58+
<StyledLabel>{searchParamAddress ? "They" : "You"} are not staked in any court</StyledLabel>
59+
) : null}
5360
{isStaked && !isLoading ? (
5461
<CourtCardsContainer>
5562
{stakeData?.jurorTokensPerCourts

web/src/pages/Dashboard/JurorInfo/Header.tsx

+35-4
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,17 @@
1-
import React from "react";
1+
import React, { useMemo } from "react";
22
import styled from "styled-components";
33

44
import { responsiveSize } from "styles/responsiveSize";
55

66
import { useToggle } from "react-use";
7+
import { useSearchParams } from "react-router-dom";
8+
import { Copiable } from "@kleros/ui-components-library";
79

810
import XIcon from "svgs/socialmedia/x.svg";
911

12+
import { DEFAULT_CHAIN, getChain } from "consts/chains";
13+
import { shortenAddress } from "utils/shortenAddress";
14+
1015
import HowItWorks from "components/HowItWorks";
1116
import JurorLevels from "components/Popup/MiniGuides/JurorLevels";
1217
import { ExternalLink } from "components/ExternalLink";
@@ -45,31 +50,57 @@ const StyledLink = styled(ExternalLink)`
4550
gap: 8px;
4651
`;
4752

53+
const StyledExternalLink = styled(ExternalLink)`
54+
font-size: ${responsiveSize(18, 22)};
55+
margin-left: ${responsiveSize(4, 8)};
56+
font-weight: 600;
57+
`;
58+
4859
interface IHeader {
4960
levelTitle: string;
5061
levelNumber: number;
5162
totalCoherentVotes: number;
5263
totalResolvedVotes: number;
64+
addressToQuery: `0x${string}`;
5365
}
5466

55-
const Header: React.FC<IHeader> = ({ levelTitle, levelNumber, totalCoherentVotes, totalResolvedVotes }) => {
67+
const Header: React.FC<IHeader> = ({
68+
levelTitle,
69+
levelNumber,
70+
totalCoherentVotes,
71+
totalResolvedVotes,
72+
addressToQuery,
73+
}) => {
5674
const [isJurorLevelsMiniGuideOpen, toggleJurorLevelsMiniGuide] = useToggle(false);
75+
const [searchParams] = useSearchParams();
5776

5877
const coherencePercentage = parseFloat(((totalCoherentVotes / Math.max(totalResolvedVotes, 1)) * 100).toFixed(2));
5978
const courtUrl = window.location.origin;
6079
const xPostText = `Hey I've been busy as a Juror on the Kleros court, check out my score: \n\nLevel: ${levelNumber} (${levelTitle})\nCoherence Percentage: ${coherencePercentage}%\nCoherent Votes: ${totalCoherentVotes}/${totalResolvedVotes}\n\nBe a juror with me! ➡️ ${courtUrl}`;
6180
const xShareUrl = `https://twitter.com/intent/tweet?text=${encodeURIComponent(xPostText)}`;
81+
const searchParamAddress = searchParams.get("address")?.toLowerCase();
82+
83+
const addressExplorerLink = useMemo(() => {
84+
return `${getChain(DEFAULT_CHAIN)?.blockExplorers?.default.url}/address/${addressToQuery}`;
85+
}, [addressToQuery]);
6286

6387
return (
6488
<Container>
65-
<StyledTitle>Juror Dashboard</StyledTitle>
89+
<StyledTitle>
90+
Juror Dashboard -
91+
<Copiable copiableContent={addressToQuery} info="Copy Address">
92+
<StyledExternalLink to={addressExplorerLink} target="_blank" rel="noopener noreferrer">
93+
{shortenAddress(addressToQuery)}
94+
</StyledExternalLink>
95+
</Copiable>
96+
</StyledTitle>
6697
<LinksContainer>
6798
<HowItWorks
6899
isMiniGuideOpen={isJurorLevelsMiniGuideOpen}
69100
toggleMiniGuide={toggleJurorLevelsMiniGuide}
70101
MiniGuideComponent={JurorLevels}
71102
/>
72-
{totalResolvedVotes > 0 ? (
103+
{totalResolvedVotes > 0 && !searchParamAddress ? (
73104
<StyledLink to={xShareUrl} target="_blank" rel="noreferrer">
74105
<StyledXIcon /> <span>Share your juror score</span>
75106
</StyledLink>

web/src/pages/Dashboard/JurorInfo/JurorRewards.tsx

+6-3
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,12 @@ const tooltipMsg =
2626
"is coherent with the final ruling receive the Juror Rewards composed of " +
2727
"arbitration fees (ETH) + PNK redistribution between jurors.";
2828

29-
const JurorRewards: React.FC = () => {
30-
const { address } = useAccount();
31-
const { data } = useUserQuery(address?.toLowerCase() as `0x${string}`);
29+
interface IJurorRewards {
30+
addressToQuery: `0x${string}`;
31+
}
32+
33+
const JurorRewards: React.FC<IJurorRewards> = ({ addressToQuery }) => {
34+
const { data } = useUserQuery(addressToQuery);
3235
const coinIds = [CoinIds.PNK, CoinIds.ETH];
3336
const { prices: pricesData } = useCoinPrice(coinIds);
3437

web/src/pages/Dashboard/JurorInfo/index.tsx

+9-8
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
import React from "react";
22
import styled, { css } from "styled-components";
33

4-
import { useAccount } from "wagmi";
5-
64
import { Card as _Card } from "@kleros/ui-components-library";
75

86
import { getUserLevelData } from "utils/userLevelCalculation";
@@ -39,9 +37,12 @@ const Card = styled(_Card)`
3937
)}
4038
`;
4139

42-
const JurorInfo: React.FC = () => {
43-
const { address } = useAccount();
44-
const { data } = useUserQuery(address?.toLowerCase() as `0x${string}`);
40+
interface IJurorInfo {
41+
addressToQuery: `0x${string}`;
42+
}
43+
44+
const JurorInfo: React.FC<IJurorInfo> = ({ addressToQuery }) => {
45+
const { data } = useUserQuery(addressToQuery);
4546
// TODO check graph schema
4647
const coherenceScore = data?.user ? parseInt(data?.user?.coherenceScore) : 0;
4748
const totalCoherentVotes = data?.user ? parseInt(data?.user?.totalCoherentVotes) : 0;
@@ -55,12 +56,12 @@ const JurorInfo: React.FC = () => {
5556
<Header
5657
levelTitle={userLevelData.title}
5758
levelNumber={userLevelData.level}
58-
{...{ totalCoherentVotes, totalResolvedVotes }}
59+
{...{ totalCoherentVotes, totalResolvedVotes, addressToQuery }}
5960
/>
6061
<Card>
6162
<PixelArt level={userLevelData.level} width="189px" height="189px" />
62-
<Coherence userLevelData={userLevelData} isMiniGuide={false} {...{ totalCoherentVotes, totalResolvedVotes }} />
63-
<JurorRewards />
63+
<Coherence isMiniGuide={false} {...{ userLevelData, totalCoherentVotes, totalResolvedVotes }} />
64+
<JurorRewards {...{ addressToQuery }} />
6465
</Card>
6566
</Container>
6667
);

0 commit comments

Comments
 (0)