Skip to content

Commit c6d8417

Browse files
committed
feat(web): structure for top jurors component with some hardcoded values
1 parent e5b545c commit c6d8417

File tree

5 files changed

+301
-4
lines changed

5 files changed

+301
-4
lines changed

web/src/components/ConnectWallet/AccountDisplay.tsx

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -96,8 +96,15 @@ const StyledAvatar = styled.img<{ size: `${number}` }>`
9696
height: ${({ size }) => size + "px"};
9797
`;
9898

99-
export const IdenticonOrAvatar: React.FC<{ size: `${number}` }> = ({ size } = { size: "16" }) => {
100-
const { address } = useAccount();
99+
interface IIdenticonOrAvatar {
100+
size?: `${number}`;
101+
address?: `0x${string}`;
102+
}
103+
104+
export const IdenticonOrAvatar: React.FC<IIdenticonOrAvatar> = ({ size = "16", address: propAddress }) => {
105+
const { address: defaultAddress } = useAccount();
106+
const address = propAddress || defaultAddress;
107+
101108
const { data: name } = useEnsName({
102109
address,
103110
chainId: 1,
@@ -106,19 +113,27 @@ export const IdenticonOrAvatar: React.FC<{ size: `${number}` }> = ({ size } = {
106113
name,
107114
chainId: 1,
108115
});
116+
109117
return avatar ? (
110118
<StyledAvatar src={avatar} alt="avatar" size={size} />
111119
) : (
112120
<StyledIdenticon size={size} string={address} />
113121
);
114122
};
115123

116-
export const AddressOrName: React.FC = () => {
117-
const { address } = useAccount();
124+
interface IAddressOrName {
125+
address?: `0x${string}`;
126+
}
127+
128+
export const AddressOrName: React.FC<IAddressOrName> = ({ address: propAddress }) => {
129+
const { address: defaultAddress } = useAccount();
130+
const address = propAddress || defaultAddress;
131+
118132
const { data } = useEnsName({
119133
address,
120134
chainId: 1,
121135
});
136+
122137
return <label>{data ?? (address && shortenAddress(address))}</label>;
123138
};
124139

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
import React from "react";
2+
import styled from "styled-components";
3+
import { IdenticonOrAvatar, AddressOrName } from "components/ConnectWallet/AccountDisplay";
4+
import EthIcon from "assets/svgs/icons/eth.svg";
5+
import PnkIcon from "assets/svgs/icons/kleros.svg";
6+
7+
const Container = styled.div`
8+
display: flex;
9+
justify-content: space-between;
10+
width: 100%;
11+
height: 100%;
12+
background-color: ${({ theme }) => theme.whiteBackground};
13+
padding: 19.5px 32px;
14+
border 1px solid ${({ theme }) => theme.stroke};
15+
border-top: none;
16+
align-items: center;
17+
18+
label {
19+
font-size: 16px;
20+
}
21+
`;
22+
23+
const JurorTitle = styled.div`
24+
display: flex;
25+
gap: 36px;
26+
align-items: center;
27+
width: 372px;
28+
`;
29+
30+
const LogoAndAddress = styled.div`
31+
display: flex;
32+
gap: 10px;
33+
align-items: center;
34+
35+
canvas {
36+
width: 20px;
37+
height: 20px;
38+
border-radius: 10%;
39+
}
40+
`;
41+
42+
const JurorData = styled.div`
43+
display: flex;
44+
justify-content: space-between;
45+
width: 100%;
46+
flex-wrap: wrap;
47+
gap: calc(24px + (48 - 24) * ((100vw - 300px) / (1250 - 300)));
48+
`;
49+
50+
const RewardsAndCoherency = styled.div`
51+
display: flex;
52+
gap: calc(52px + (104 - 52) * (min(max(100vw, 375px), 1250px) - 375px) / 875);
53+
`;
54+
55+
const Rewards = styled.div`
56+
display: flex;
57+
gap: 8px;
58+
align-items: center;
59+
label {
60+
font-weight: 600;
61+
}
62+
width: 132px;
63+
`;
64+
65+
const Coherency = styled.div`
66+
align-items: center;
67+
label {
68+
font-weight: 600;
69+
}
70+
`;
71+
72+
const StyledIcon = styled.div`
73+
width: 16px;
74+
height: 16px;
75+
76+
path {
77+
fill: ${({ theme }) => theme.secondaryPurple};
78+
}
79+
`;
80+
81+
const StyledIdenticonOrAvatar = styled(IdenticonOrAvatar)``;
82+
83+
interface IJurorCard {
84+
id: number;
85+
address: `0x${string}`;
86+
}
87+
88+
const JurorCard: React.FC<IJurorCard> = ({ id, address }) => {
89+
const ethReward = "11";
90+
const pnkReward = "30K";
91+
const coherentVotes = "10/12";
92+
93+
return (
94+
<Container>
95+
<JurorTitle>
96+
<label>{id}</label>
97+
<LogoAndAddress>
98+
<StyledIdenticonOrAvatar address={address} />
99+
<AddressOrName address={address} />
100+
</LogoAndAddress>
101+
</JurorTitle>
102+
<JurorData>
103+
<RewardsAndCoherency>
104+
<Rewards>
105+
<label>{ethReward}</label>
106+
<StyledIcon as={EthIcon} />
107+
<label>+</label>
108+
<label>{pnkReward}</label>
109+
<StyledIcon as={PnkIcon} />
110+
</Rewards>
111+
<Coherency>
112+
<label>{coherentVotes}</label>
113+
</Coherency>
114+
</RewardsAndCoherency>
115+
{/*"How it works" section here*/}
116+
</JurorData>
117+
</Container>
118+
);
119+
};
120+
121+
export default JurorCard;
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
import React from "react";
2+
import styled, { css } from "styled-components";
3+
import { landscapeStyle } from "styles/landscapeStyle";
4+
import WithHelpTooltip from "pages/Dashboard/WithHelpTooltip";
5+
import BookOpenIcon from "tsx:assets/svgs/icons/book-open.svg";
6+
7+
const Container = styled.div`
8+
display: flex;
9+
justify-content: space-between;
10+
11+
width: 100%;
12+
height: 100%;
13+
background-color: ${({ theme }) => theme.lightBlue};
14+
padding: 18.5px 32px;
15+
border 1px solid ${({ theme }) => theme.stroke};
16+
border-top-left-radius: 3px;
17+
border-top-right-radius: 3px;
18+
`;
19+
20+
const JurorData = styled.div`
21+
display: flex;
22+
align-items: center;
23+
justify-content: space-between;
24+
width: 100%;
25+
flex-wrap: wrap;
26+
gap: calc(24px + (48 - 24) * ((100vw - 300px) / (1250 - 300)));
27+
`;
28+
29+
const JurorTitle = styled.div`
30+
display: none;
31+
gap: 36px;
32+
align-items: center;
33+
label {
34+
font-weight: 400;
35+
font-size: 14px;
36+
line-height: 19px;
37+
color: ${({ theme }) => theme.secondaryText} !important;
38+
}
39+
40+
${landscapeStyle(
41+
() =>
42+
css`
43+
display: flex;
44+
width: 372px;
45+
`
46+
)}
47+
`;
48+
49+
const RewardsAndCoherency = styled.div`
50+
display: flex;
51+
gap: calc(52px + (104 - 52) * (min(max(100vw, 375px), 1250px) - 375px) / 875);
52+
`;
53+
54+
const Rewards = styled.div`
55+
width: 132px;
56+
`;
57+
58+
const HowItWorks = styled.div`
59+
display: flex;
60+
align-items: center;
61+
gap: 8px;
62+
63+
label {
64+
color: ${({ theme }) => theme.primaryBlue};
65+
}
66+
67+
svg {
68+
path {
69+
fill: ${({ theme }) => theme.primaryBlue};
70+
}
71+
}
72+
`;
73+
74+
const totalRewardsTooltipMsg =
75+
"Users have an economic interest in serving as jurors in Kleros: " +
76+
"collecting the Juror Rewards in exchange for their work. Each juror who " +
77+
"is coherent with the final ruling receive the Juror Rewards composed of " +
78+
"arbitration fees (ETH) + PNK redistribution between jurors.";
79+
80+
const coherentVotesTooltipMsg =
81+
"This is the ratio of coherent votes made by a juror: " +
82+
"the number in the left is the number of times where the juror " +
83+
"voted coherently and the number in the right is the total number of times " +
84+
"the juror voted";
85+
86+
const TopJurorsHeader: React.FC = () => {
87+
return (
88+
<Container>
89+
<JurorTitle>
90+
<label>#</label>
91+
<label>Juror</label>
92+
</JurorTitle>
93+
<JurorData>
94+
<RewardsAndCoherency>
95+
<Rewards>
96+
<WithHelpTooltip place="top" tooltipMsg={totalRewardsTooltipMsg}>
97+
<label> Total Rewards </label>
98+
</WithHelpTooltip>
99+
</Rewards>
100+
101+
<WithHelpTooltip place="top" tooltipMsg={coherentVotesTooltipMsg}>
102+
<label> Coherent Votes </label>
103+
</WithHelpTooltip>
104+
</RewardsAndCoherency>
105+
<HowItWorks>
106+
<BookOpenIcon />
107+
<label> How it works </label>
108+
</HowItWorks>
109+
</JurorData>
110+
</Container>
111+
);
112+
};
113+
114+
export default TopJurorsHeader;
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import React from "react";
2+
import styled from "styled-components";
3+
import { SkeletonDisputeListItem } from "components/StyledSkeleton";
4+
import { isUndefined } from "utils/index";
5+
import TopJurorsHeader from "./TopJurorsHeader";
6+
import JurorCard from "./JurorCard";
7+
8+
const Container = styled.div`
9+
margin-top: calc(64px + (80 - 64) * (min(max(100vw, 375px), 1250px) - 375px) / 875);
10+
`;
11+
12+
const Title = styled.h1`
13+
margin-bottom: calc(16px + (48 - 16) * (min(max(100vw, 375px), 1250px) - 375px) / 875);
14+
`;
15+
16+
const ListContainer = styled.div`
17+
display: flex;
18+
flex-direction: column;
19+
justify-content: center;
20+
`;
21+
22+
const TopJurors: React.FC<ITopJurors> = () => {
23+
const jurors = [
24+
{ id: 1, address: "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045" },
25+
{ id: 2, address: "0x74199ddaC9607A3a694011793f674FA1E0d0Fe2D" },
26+
{ id: 3, address: "0x96BFc2d3d2b6fdE87D9271a8684a45d93087139d" },
27+
{ id: 4, address: "0x1f9090aaE28b8a3dCeaDf281B0F12828e676c326" },
28+
{ id: 5, address: "0x10F5d45854e038071485AC9e402308cF80D2d2fE" },
29+
];
30+
31+
return (
32+
<Container>
33+
<Title>Top Jurors</Title>
34+
<ListContainer>
35+
<TopJurorsHeader />
36+
{isUndefined(jurors)
37+
? [...Array(5)].map((_, i) => <SkeletonDisputeListItem key={i} />)
38+
: jurors.map((juror) => {
39+
return <JurorCard key={juror.id} {...juror} />;
40+
})}
41+
</ListContainer>
42+
</Container>
43+
);
44+
};
45+
export default TopJurors;

web/src/pages/Home/index.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import Community from "./Community";
66
import HeroImage from "./HeroImage";
77
import { HomePageProvider } from "hooks/useHomePageContext";
88
import { getOneYearAgoTimestamp } from "utils/date";
9+
import TopJurors from "./TopJurors";
910

1011
const Container = styled.div`
1112
width: 100%;
@@ -24,6 +25,7 @@ const Home: React.FC = () => {
2425
<Container>
2526
<CourtOverview />
2627
<LatestCases />
28+
<TopJurors />
2729
<Community />
2830
</Container>
2931
</HomePageProvider>

0 commit comments

Comments
 (0)