From c6d841725cb1b8d123e7cda3de4ee94e1e497b1b Mon Sep 17 00:00:00 2001 From: marino <102478601+kemuru@users.noreply.github.com> Date: Fri, 20 Oct 2023 20:33:10 +0200 Subject: [PATCH 01/11] feat(web): structure for top jurors component with some hardcoded values --- .../ConnectWallet/AccountDisplay.tsx | 23 +++- web/src/pages/Home/TopJurors/JurorCard.tsx | 121 ++++++++++++++++++ .../pages/Home/TopJurors/TopJurorsHeader.tsx | 114 +++++++++++++++++ web/src/pages/Home/TopJurors/index.tsx | 45 +++++++ web/src/pages/Home/index.tsx | 2 + 5 files changed, 301 insertions(+), 4 deletions(-) create mode 100644 web/src/pages/Home/TopJurors/JurorCard.tsx create mode 100644 web/src/pages/Home/TopJurors/TopJurorsHeader.tsx create mode 100644 web/src/pages/Home/TopJurors/index.tsx diff --git a/web/src/components/ConnectWallet/AccountDisplay.tsx b/web/src/components/ConnectWallet/AccountDisplay.tsx index d39e7c60e..1d677637e 100644 --- a/web/src/components/ConnectWallet/AccountDisplay.tsx +++ b/web/src/components/ConnectWallet/AccountDisplay.tsx @@ -96,8 +96,15 @@ const StyledAvatar = styled.img<{ size: `${number}` }>` height: ${({ size }) => size + "px"}; `; -export const IdenticonOrAvatar: React.FC<{ size: `${number}` }> = ({ size } = { size: "16" }) => { - const { address } = useAccount(); +interface IIdenticonOrAvatar { + size?: `${number}`; + address?: `0x${string}`; +} + +export const IdenticonOrAvatar: React.FC = ({ size = "16", address: propAddress }) => { + const { address: defaultAddress } = useAccount(); + const address = propAddress || defaultAddress; + const { data: name } = useEnsName({ address, chainId: 1, @@ -106,6 +113,7 @@ export const IdenticonOrAvatar: React.FC<{ size: `${number}` }> = ({ size } = { name, chainId: 1, }); + return avatar ? ( ) : ( @@ -113,12 +121,19 @@ export const IdenticonOrAvatar: React.FC<{ size: `${number}` }> = ({ size } = { ); }; -export const AddressOrName: React.FC = () => { - const { address } = useAccount(); +interface IAddressOrName { + address?: `0x${string}`; +} + +export const AddressOrName: React.FC = ({ address: propAddress }) => { + const { address: defaultAddress } = useAccount(); + const address = propAddress || defaultAddress; + const { data } = useEnsName({ address, chainId: 1, }); + return ; }; diff --git a/web/src/pages/Home/TopJurors/JurorCard.tsx b/web/src/pages/Home/TopJurors/JurorCard.tsx new file mode 100644 index 000000000..798ae9e4a --- /dev/null +++ b/web/src/pages/Home/TopJurors/JurorCard.tsx @@ -0,0 +1,121 @@ +import React from "react"; +import styled from "styled-components"; +import { IdenticonOrAvatar, AddressOrName } from "components/ConnectWallet/AccountDisplay"; +import EthIcon from "assets/svgs/icons/eth.svg"; +import PnkIcon from "assets/svgs/icons/kleros.svg"; + +const Container = styled.div` + display: flex; + justify-content: space-between; + width: 100%; + height: 100%; + background-color: ${({ theme }) => theme.whiteBackground}; + padding: 19.5px 32px; + border 1px solid ${({ theme }) => theme.stroke}; + border-top: none; + align-items: center; + + label { + font-size: 16px; + } +`; + +const JurorTitle = styled.div` + display: flex; + gap: 36px; + align-items: center; + width: 372px; +`; + +const LogoAndAddress = styled.div` + display: flex; + gap: 10px; + align-items: center; + + canvas { + width: 20px; + height: 20px; + border-radius: 10%; + } +`; + +const JurorData = styled.div` + display: flex; + justify-content: space-between; + width: 100%; + flex-wrap: wrap; + gap: calc(24px + (48 - 24) * ((100vw - 300px) / (1250 - 300))); +`; + +const RewardsAndCoherency = styled.div` + display: flex; + gap: calc(52px + (104 - 52) * (min(max(100vw, 375px), 1250px) - 375px) / 875); +`; + +const Rewards = styled.div` + display: flex; + gap: 8px; + align-items: center; + label { + font-weight: 600; + } + width: 132px; +`; + +const Coherency = styled.div` + align-items: center; + label { + font-weight: 600; + } +`; + +const StyledIcon = styled.div` + width: 16px; + height: 16px; + + path { + fill: ${({ theme }) => theme.secondaryPurple}; + } +`; + +const StyledIdenticonOrAvatar = styled(IdenticonOrAvatar)``; + +interface IJurorCard { + id: number; + address: `0x${string}`; +} + +const JurorCard: React.FC = ({ id, address }) => { + const ethReward = "11"; + const pnkReward = "30K"; + const coherentVotes = "10/12"; + + return ( + + + + + + + + + + + + + + + + + + + + + + {/*"How it works" section here*/} + + + ); +}; + +export default JurorCard; diff --git a/web/src/pages/Home/TopJurors/TopJurorsHeader.tsx b/web/src/pages/Home/TopJurors/TopJurorsHeader.tsx new file mode 100644 index 000000000..e951c42c2 --- /dev/null +++ b/web/src/pages/Home/TopJurors/TopJurorsHeader.tsx @@ -0,0 +1,114 @@ +import React from "react"; +import styled, { css } from "styled-components"; +import { landscapeStyle } from "styles/landscapeStyle"; +import WithHelpTooltip from "pages/Dashboard/WithHelpTooltip"; +import BookOpenIcon from "tsx:assets/svgs/icons/book-open.svg"; + +const Container = styled.div` + display: flex; + justify-content: space-between; + + width: 100%; + height: 100%; + background-color: ${({ theme }) => theme.lightBlue}; + padding: 18.5px 32px; + border 1px solid ${({ theme }) => theme.stroke}; + border-top-left-radius: 3px; + border-top-right-radius: 3px; +`; + +const JurorData = styled.div` + display: flex; + align-items: center; + justify-content: space-between; + width: 100%; + flex-wrap: wrap; + gap: calc(24px + (48 - 24) * ((100vw - 300px) / (1250 - 300))); +`; + +const JurorTitle = styled.div` + display: none; + gap: 36px; + align-items: center; + label { + font-weight: 400; + font-size: 14px; + line-height: 19px; + color: ${({ theme }) => theme.secondaryText} !important; + } + + ${landscapeStyle( + () => + css` + display: flex; + width: 372px; + ` + )} +`; + +const RewardsAndCoherency = styled.div` + display: flex; + gap: calc(52px + (104 - 52) * (min(max(100vw, 375px), 1250px) - 375px) / 875); +`; + +const Rewards = styled.div` + width: 132px; +`; + +const HowItWorks = styled.div` + display: flex; + align-items: center; + gap: 8px; + + label { + color: ${({ theme }) => theme.primaryBlue}; + } + + svg { + path { + fill: ${({ theme }) => theme.primaryBlue}; + } + } +`; + +const totalRewardsTooltipMsg = + "Users have an economic interest in serving as jurors in Kleros: " + + "collecting the Juror Rewards in exchange for their work. Each juror who " + + "is coherent with the final ruling receive the Juror Rewards composed of " + + "arbitration fees (ETH) + PNK redistribution between jurors."; + +const coherentVotesTooltipMsg = + "This is the ratio of coherent votes made by a juror: " + + "the number in the left is the number of times where the juror " + + "voted coherently and the number in the right is the total number of times " + + "the juror voted"; + +const TopJurorsHeader: React.FC = () => { + return ( + + + + + + + + + + + + + + + + + + + + + + + + ); +}; + +export default TopJurorsHeader; diff --git a/web/src/pages/Home/TopJurors/index.tsx b/web/src/pages/Home/TopJurors/index.tsx new file mode 100644 index 000000000..2bef8ba56 --- /dev/null +++ b/web/src/pages/Home/TopJurors/index.tsx @@ -0,0 +1,45 @@ +import React from "react"; +import styled from "styled-components"; +import { SkeletonDisputeListItem } from "components/StyledSkeleton"; +import { isUndefined } from "utils/index"; +import TopJurorsHeader from "./TopJurorsHeader"; +import JurorCard from "./JurorCard"; + +const Container = styled.div` + margin-top: calc(64px + (80 - 64) * (min(max(100vw, 375px), 1250px) - 375px) / 875); +`; + +const Title = styled.h1` + margin-bottom: calc(16px + (48 - 16) * (min(max(100vw, 375px), 1250px) - 375px) / 875); +`; + +const ListContainer = styled.div` + display: flex; + flex-direction: column; + justify-content: center; +`; + +const TopJurors: React.FC = () => { + const jurors = [ + { id: 1, address: "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045" }, + { id: 2, address: "0x74199ddaC9607A3a694011793f674FA1E0d0Fe2D" }, + { id: 3, address: "0x96BFc2d3d2b6fdE87D9271a8684a45d93087139d" }, + { id: 4, address: "0x1f9090aaE28b8a3dCeaDf281B0F12828e676c326" }, + { id: 5, address: "0x10F5d45854e038071485AC9e402308cF80D2d2fE" }, + ]; + + return ( + + Top Jurors + + + {isUndefined(jurors) + ? [...Array(5)].map((_, i) => ) + : jurors.map((juror) => { + return ; + })} + + + ); +}; +export default TopJurors; diff --git a/web/src/pages/Home/index.tsx b/web/src/pages/Home/index.tsx index 45c9b7686..d7220c707 100644 --- a/web/src/pages/Home/index.tsx +++ b/web/src/pages/Home/index.tsx @@ -6,6 +6,7 @@ import Community from "./Community"; import HeroImage from "./HeroImage"; import { HomePageProvider } from "hooks/useHomePageContext"; import { getOneYearAgoTimestamp } from "utils/date"; +import TopJurors from "./TopJurors"; const Container = styled.div` width: 100%; @@ -24,6 +25,7 @@ const Home: React.FC = () => { + From 7e92e10dbc01a6124d8c48b19bdcbc45392cad24 Mon Sep 17 00:00:00 2001 From: marino <102478601+kemuru@users.noreply.github.com> Date: Sun, 22 Oct 2023 06:50:05 +0200 Subject: [PATCH 02/11] refactor(web): abstraction user level coherency, level calculation, pixelart width and heights --- .../pages/Dashboard/JurorInfo/PixelArt.tsx | 31 ++++++++++++++----- web/src/pages/Dashboard/JurorInfo/index.tsx | 22 +++---------- web/src/pages/Home/TopJurors/JurorCard.tsx | 22 ++++++++++++- web/src/pages/Home/TopJurors/index.tsx | 2 +- web/src/utils/userLevelCalculation.tsx | 20 ++++++++++++ 5 files changed, 69 insertions(+), 28 deletions(-) create mode 100644 web/src/utils/userLevelCalculation.tsx diff --git a/web/src/pages/Dashboard/JurorInfo/PixelArt.tsx b/web/src/pages/Dashboard/JurorInfo/PixelArt.tsx index 2545ac9ed..7b360be1d 100644 --- a/web/src/pages/Dashboard/JurorInfo/PixelArt.tsx +++ b/web/src/pages/Dashboard/JurorInfo/PixelArt.tsx @@ -7,33 +7,48 @@ import socratesImage from "assets/pngs/dashboard/socrates.png"; import platoImage from "assets/pngs/dashboard/plato.png"; import aristotelesImage from "assets/pngs/dashboard/aristoteles.png"; -const StyledImage = styled.img<{ show: boolean }>` - width: 189px; - height: 189px; +interface IStyledImage { + show: boolean; + width: number | string; + height: number | string; +} + +const StyledImage = styled.img` + width: ${({ width }) => width}; + height: ${({ height }) => height}; display: ${({ show }) => (show ? "block" : "none")}; `; -const StyledSkeleton = styled(Skeleton)` - width: 189px; - height: 189px; +interface IStyledSkeleton { + width: number | string; + height: number | string; +} + +const StyledSkeleton = styled(Skeleton)` + width: ${({ width }) => width}; + height: ${({ height }) => height}; `; const images = [diogenesImage, pythagorasImage, socratesImage, platoImage, aristotelesImage]; interface IPixelArt { level: number; + width: number | string; + height: number | string; } -const PixelArt: React.FC = ({ level }) => { +const PixelArt: React.FC = ({ level, width, height }) => { const [imageLoaded, setImageLoaded] = useState(false); return (
- {!imageLoaded && } + {!imageLoaded && } setImageLoaded(true)} show={imageLoaded} + width={width} + height={height} />
); diff --git a/web/src/pages/Dashboard/JurorInfo/index.tsx b/web/src/pages/Dashboard/JurorInfo/index.tsx index 9a2e803b8..94e22bc7a 100644 --- a/web/src/pages/Dashboard/JurorInfo/index.tsx +++ b/web/src/pages/Dashboard/JurorInfo/index.tsx @@ -7,6 +7,7 @@ import JurorRewards from "./JurorRewards"; import PixelArt from "./PixelArt"; import { useAccount } from "wagmi"; import { useUserQuery } from "queries/useUser"; +import { getCoherencyScore, getUserLevelData } from "utils/userLevelCalculation"; // import StakingRewards from "./StakingRewards"; const Container = styled.div``; @@ -35,35 +36,20 @@ const Card = styled(_Card)` )} `; -const levelTitles = [ - { scoreRange: [0, 20], level: 0, title: "Diogenes" }, - { scoreRange: [20, 40], level: 1, title: "Pythagoras" }, - { scoreRange: [40, 60], level: 2, title: "Socrates" }, - { scoreRange: [60, 80], level: 3, title: "Plato" }, - { scoreRange: [80, 100], level: 4, title: "Aristotle" }, -]; - -const calculateCoherencyScore = (totalCoherent: number, totalDisputes: number): number => - totalCoherent / (Math.max(totalDisputes, 1) + 10); - const JurorInfo: React.FC = () => { const { address } = useAccount(); const { data } = useUserQuery(address?.toLowerCase()); const totalCoherent = data?.user ? parseInt(data?.user?.totalCoherent) : 0; const totalResolvedDisputes = data?.user ? parseInt(data?.user?.totalResolvedDisputes) : 1; - const coherencyScore = calculateCoherencyScore(totalCoherent, totalResolvedDisputes); - const roundedCoherencyScore = Math.round(coherencyScore * 100); - const userLevelData = - levelTitles.find(({ scoreRange }) => { - return roundedCoherencyScore >= scoreRange[0] && roundedCoherencyScore < scoreRange[1]; - }) ?? levelTitles[0]; + const coherencyScore = getCoherencyScore(totalCoherent, totalResolvedDisputes); + const userLevelData = getUserLevelData(totalCoherent, totalResolvedDisputes); return (
Juror Dashboard
- + = ({ id, address }) => { const pnkReward = "30K"; const coherentVotes = "10/12"; + const { data } = useUserQuery(address?.toLowerCase()); + const totalCoherent = data?.user ? parseInt(data?.user?.totalCoherent) : 0; + const totalResolvedDisputes = data?.user ? parseInt(data?.user?.totalResolvedDisputes) : 1; + // const userLevelData = getUserLevelData(totalCoherent, totalResolvedDisputes); + const userLevelData = { + level: 4, + }; + return ( @@ -112,7 +129,10 @@ const JurorCard: React.FC = ({ id, address }) => { - {/*"How it works" section here*/} + + + +
); diff --git a/web/src/pages/Home/TopJurors/index.tsx b/web/src/pages/Home/TopJurors/index.tsx index 2bef8ba56..73f4d0baa 100644 --- a/web/src/pages/Home/TopJurors/index.tsx +++ b/web/src/pages/Home/TopJurors/index.tsx @@ -19,7 +19,7 @@ const ListContainer = styled.div` justify-content: center; `; -const TopJurors: React.FC = () => { +const TopJurors: React.FC = () => { const jurors = [ { id: 1, address: "0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045" }, { id: 2, address: "0x74199ddaC9607A3a694011793f674FA1E0d0Fe2D" }, diff --git a/web/src/utils/userLevelCalculation.tsx b/web/src/utils/userLevelCalculation.tsx new file mode 100644 index 000000000..56877367e --- /dev/null +++ b/web/src/utils/userLevelCalculation.tsx @@ -0,0 +1,20 @@ +export const levelTitles = [ + { scoreRange: [0, 20], level: 0, title: "Diogenes" }, + { scoreRange: [20, 40], level: 1, title: "Pythagoras" }, + { scoreRange: [40, 60], level: 2, title: "Socrates" }, + { scoreRange: [60, 80], level: 3, title: "Plato" }, + { scoreRange: [80, 100], level: 4, title: "Aristotle" }, +]; + +export const getCoherencyScore = (totalCoherent: number, totalDisputes: number): number => + totalCoherent / (Math.max(totalDisputes, 1) + 10); + +export const getUserLevelData = (totalCoherent: number, totalDisputes: number) => { + const coherencyScore = getCoherencyScore(totalCoherent, totalDisputes); + const roundedCoherencyScore = Math.round(coherencyScore * 100); + return ( + levelTitles.find(({ scoreRange }) => { + return roundedCoherencyScore >= scoreRange[0] && roundedCoherencyScore < scoreRange[1]; + }) ?? levelTitles[0] + ); +}; From eb442debb407f660dcf299dafa28ce127d7becf0 Mon Sep 17 00:00:00 2001 From: marino <102478601+kemuru@users.noreply.github.com> Date: Sun, 22 Oct 2023 19:02:59 +0200 Subject: [PATCH 03/11] feat(web): better styling in mobile version --- web/src/pages/Home/TopJurors/JurorCard.tsx | 112 +++++++++++------- .../pages/Home/TopJurors/TopJurorsHeader.tsx | 89 ++++++++------ 2 files changed, 122 insertions(+), 79 deletions(-) diff --git a/web/src/pages/Home/TopJurors/JurorCard.tsx b/web/src/pages/Home/TopJurors/JurorCard.tsx index 9a645ae69..9cc0b971a 100644 --- a/web/src/pages/Home/TopJurors/JurorCard.tsx +++ b/web/src/pages/Home/TopJurors/JurorCard.tsx @@ -1,5 +1,6 @@ import React from "react"; -import styled from "styled-components"; +import styled, { css } from "styled-components"; +import { landscapeStyle } from "styles/landscapeStyle"; import { IdenticonOrAvatar, AddressOrName } from "components/ConnectWallet/AccountDisplay"; import EthIcon from "assets/svgs/icons/eth.svg"; import PnkIcon from "assets/svgs/icons/kleros.svg"; @@ -7,27 +8,29 @@ import PixelArt from "pages/Dashboard/JurorInfo/PixelArt"; import { getUserLevelData } from "utils/userLevelCalculation"; import { useUserQuery } from "hooks/queries/useUser"; -const Container = styled.div` +const Container = styled.div<{ id?: number }>` display: flex; justify-content: space-between; + flex-wrap: wrap; width: 100%; height: 100%; background-color: ${({ theme }) => theme.whiteBackground}; - padding: 19.5px 32px; + padding: 24px; border 1px solid ${({ theme }) => theme.stroke}; - border-top: none; + border-top: ${({ id }) => (id === 1 ? "" : "none")}; align-items: center; label { font-size: 16px; } -`; -const JurorTitle = styled.div` - display: flex; - gap: 36px; - align-items: center; - width: 372px; + ${landscapeStyle( + () => css` + gap: 0px; + padding: 15.55px 32px; + flex-wrap: nowrap; + ` + )} `; const LogoAndAddress = styled.div` @@ -42,17 +45,32 @@ const LogoAndAddress = styled.div` } `; -const JurorData = styled.div` +const TitleAndRewardsAndCoherency = styled.div` display: flex; - justify-content: space-between; - width: 100%; - flex-wrap: wrap; - gap: calc(24px + (48 - 24) * ((100vw - 300px) / (1250 - 300))); + flex-direction: column; + gap: 16px; + + ${landscapeStyle( + () => + css` + flex-direction: row; + gap: 32px; + ` + )} `; -const RewardsAndCoherency = styled.div` +const JurorTitle = styled.div` display: flex; - gap: calc(52px + (104 - 52) * (min(max(100vw, 375px), 1250px) - 375px) / 875); + gap: 16px; + align-items: center; + justify-content: flex-start; + + ${landscapeStyle( + () => css` + width: calc(140px + (260 - 140) * (min(max(100vw, 375px), 1250px) - 375px) / 875); + gap: 36px; + ` + )} `; const Rewards = styled.div` @@ -63,13 +81,23 @@ const Rewards = styled.div` font-weight: 600; } width: 132px; + flex-wrap: wrap; + + ${landscapeStyle( + () => + css` + width: calc(80px + (180 - 80) * (min(max(100vw, 375px), 1250px) - 375px) / 875); + ` + )} `; const Coherency = styled.div` + display: flex; align-items: center; label { font-weight: 600; } + flex-wrap: wrap; `; const StyledIcon = styled.div` @@ -108,32 +136,30 @@ const JurorCard: React.FC = ({ id, address }) => { }; return ( - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + ); }; diff --git a/web/src/pages/Home/TopJurors/TopJurorsHeader.tsx b/web/src/pages/Home/TopJurors/TopJurorsHeader.tsx index e951c42c2..11adba4d0 100644 --- a/web/src/pages/Home/TopJurors/TopJurorsHeader.tsx +++ b/web/src/pages/Home/TopJurors/TopJurorsHeader.tsx @@ -7,29 +7,45 @@ import BookOpenIcon from "tsx:assets/svgs/icons/book-open.svg"; const Container = styled.div` display: flex; justify-content: space-between; - width: 100%; height: 100%; background-color: ${({ theme }) => theme.lightBlue}; - padding: 18.5px 32px; + padding: 24px; border 1px solid ${({ theme }) => theme.stroke}; border-top-left-radius: 3px; border-top-right-radius: 3px; + border-bottom: none; + flex-wrap: wrap; + + ${landscapeStyle( + () => + css` + flex-wrap: nowrap; + gap: 0px; + padding: 18.6px 32px; + ` + )} `; -const JurorData = styled.div` +const TitleAndRewardsAndCoherency = styled.div` display: flex; - align-items: center; - justify-content: space-between; - width: 100%; - flex-wrap: wrap; - gap: calc(24px + (48 - 24) * ((100vw - 300px) / (1250 - 300))); + flex-direction: column; + gap: 12px; + + ${landscapeStyle( + () => + css` + flex-direction: row; + gap: 32px; + ` + )} `; const JurorTitle = styled.div` - display: none; - gap: 36px; + display: flex; + gap: 16px; align-items: center; + label { font-weight: 400; font-size: 14px; @@ -40,21 +56,23 @@ const JurorTitle = styled.div` ${landscapeStyle( () => css` - display: flex; - width: 372px; + width: calc(160px + (260 - 160) * (min(max(100vw, 375px), 1250px) - 375px) / 875); + gap: 36px; ` )} `; -const RewardsAndCoherency = styled.div` - display: flex; - gap: calc(52px + (104 - 52) * (min(max(100vw, 375px), 1250px) - 375px) / 875); -`; - const Rewards = styled.div` - width: 132px; + ${landscapeStyle( + () => + css` + width: calc(80px + (180 - 80) * (min(max(100vw, 375px), 1250px) - 375px) / 875); + ` + )} `; +const Coherency = styled.div``; + const HowItWorks = styled.div` display: flex; align-items: center; @@ -86,27 +104,26 @@ const coherentVotesTooltipMsg = const TopJurorsHeader: React.FC = () => { return ( - - - - - - - - - - - - + + + + + + + + + + + - - - - - - + + + + + + ); }; From 968d173b23eca0b8376ddfe474a9f72dee98979b Mon Sep 17 00:00:00 2001 From: marino <102478601+kemuru@users.noreply.github.com> Date: Sun, 22 Oct 2023 21:05:25 +0200 Subject: [PATCH 04/11] feat(web): slighly different style for mobile --- web/src/pages/Home/TopJurors/JurorCard.tsx | 34 ++++++++++++---- .../pages/Home/TopJurors/TopJurorsHeader.tsx | 40 +++++++++++++++---- 2 files changed, 60 insertions(+), 14 deletions(-) diff --git a/web/src/pages/Home/TopJurors/JurorCard.tsx b/web/src/pages/Home/TopJurors/JurorCard.tsx index 9cc0b971a..6072c8b1f 100644 --- a/web/src/pages/Home/TopJurors/JurorCard.tsx +++ b/web/src/pages/Home/TopJurors/JurorCard.tsx @@ -45,10 +45,10 @@ const LogoAndAddress = styled.div` } `; -const TitleAndRewardsAndCoherency = styled.div` +const PlaceAndTitleAndRewardsAndCoherency = styled.div` display: flex; flex-direction: column; - gap: 16px; + gap: 12px; ${landscapeStyle( () => @@ -59,6 +59,24 @@ const TitleAndRewardsAndCoherency = styled.div` )} `; +const JurorPlace = styled.div` + width: 100%; + + label::before { + content: "#"; + display: inline; + } + + ${landscapeStyle( + () => css` + width: calc(16px + (24 - 16) * (min(max(100vw, 375px), 1250px) - 375px) / 875); + label::before { + display: none; + } + ` + )} +`; + const JurorTitle = styled.div` display: flex; gap: 16px; @@ -67,7 +85,7 @@ const JurorTitle = styled.div` ${landscapeStyle( () => css` - width: calc(140px + (260 - 140) * (min(max(100vw, 375px), 1250px) - 375px) / 875); + width: calc(40px + (232 - 40) * (min(max(100vw, 375px), 1250px) - 375px) / 875); gap: 36px; ` )} @@ -86,7 +104,7 @@ const Rewards = styled.div` ${landscapeStyle( () => css` - width: calc(80px + (180 - 80) * (min(max(100vw, 375px), 1250px) - 375px) / 875); + width: calc(60px + (180 - 60) * (min(max(100vw, 375px), 1250px) - 375px) / 875); ` )} `; @@ -137,9 +155,11 @@ const JurorCard: React.FC = ({ id, address }) => { return ( - - + + + + @@ -155,7 +175,7 @@ const JurorCard: React.FC = ({ id, address }) => { - + diff --git a/web/src/pages/Home/TopJurors/TopJurorsHeader.tsx b/web/src/pages/Home/TopJurors/TopJurorsHeader.tsx index 11adba4d0..748e42b54 100644 --- a/web/src/pages/Home/TopJurors/TopJurorsHeader.tsx +++ b/web/src/pages/Home/TopJurors/TopJurorsHeader.tsx @@ -27,10 +27,10 @@ const Container = styled.div` )} `; -const TitleAndRewardsAndCoherency = styled.div` +const PlaceAndTitleAndRewardsAndCoherency = styled.div` display: flex; flex-direction: column; - gap: 12px; + gap: 8px; ${landscapeStyle( () => @@ -41,6 +41,30 @@ const TitleAndRewardsAndCoherency = styled.div` )} `; +const JurorPlace = styled.div` + width: 100%; + + label { + &::before { + content: "# Rank"; + visibility: visible; + } + } + + ${landscapeStyle( + () => + css` + width: calc(16px + (24 - 16) * (min(max(100vw, 375px), 1250px) - 375px) / 875); + + label { + &::before { + content: "#"; + } + } + ` + )} +`; + const JurorTitle = styled.div` display: flex; gap: 16px; @@ -56,7 +80,7 @@ const JurorTitle = styled.div` ${landscapeStyle( () => css` - width: calc(160px + (260 - 160) * (min(max(100vw, 375px), 1250px) - 375px) / 875); + width: calc(40px + (232 - 40) * (min(max(100vw, 375px), 1250px) - 375px) / 875); gap: 36px; ` )} @@ -66,7 +90,7 @@ const Rewards = styled.div` ${landscapeStyle( () => css` - width: calc(80px + (180 - 80) * (min(max(100vw, 375px), 1250px) - 375px) / 875); + width: calc(60px + (180 - 60) * (min(max(100vw, 375px), 1250px) - 375px) / 875); ` )} `; @@ -104,9 +128,11 @@ const coherentVotesTooltipMsg = const TopJurorsHeader: React.FC = () => { return ( - + + + + - @@ -119,7 +145,7 @@ const TopJurorsHeader: React.FC = () => { - + From f118da431ed80bf6ea8404e46b1c4126ec9b85fa Mon Sep 17 00:00:00 2001 From: marino <102478601+kemuru@users.noreply.github.com> Date: Mon, 23 Oct 2023 11:41:45 +0200 Subject: [PATCH 05/11] fix(web): bug styled skeleton not showing in pixelart --- web/src/pages/Dashboard/JurorInfo/index.tsx | 2 +- web/src/pages/Home/TopJurors/JurorCard.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/web/src/pages/Dashboard/JurorInfo/index.tsx b/web/src/pages/Dashboard/JurorInfo/index.tsx index 94e22bc7a..ebc9c0ddd 100644 --- a/web/src/pages/Dashboard/JurorInfo/index.tsx +++ b/web/src/pages/Dashboard/JurorInfo/index.tsx @@ -49,7 +49,7 @@ const JurorInfo: React.FC = () => {
Juror Dashboard
- + = ({ id, address }) => { - +
); From edd79fa168b0cfaa8a02ee07adc6ad818233e7d9 Mon Sep 17 00:00:00 2001 From: marino <102478601+kemuru@users.noreply.github.com> Date: Tue, 24 Oct 2023 02:22:20 +0200 Subject: [PATCH 06/11] feat(web,subgraph): real data, add coherence score to subgraph schema & mappings, abstract utils --- subgraph/schema.graphql | 1 + subgraph/src/entities/User.ts | 22 +++++++- .../queries/useTopUsersByCoherenceScore.ts | 35 ++++++++++++ web/src/hooks/queries/useUser.ts | 1 + .../pages/Dashboard/JurorInfo/Coherency.tsx | 8 +-- .../Dashboard/JurorInfo/JurorRewards.tsx | 56 +++---------------- web/src/pages/Dashboard/JurorInfo/index.tsx | 15 ++--- web/src/pages/Home/TopJurors/JurorCard.tsx | 41 +++++++------- .../pages/Home/TopJurors/TopJurorsHeader.tsx | 5 +- web/src/pages/Home/TopJurors/index.tsx | 22 ++++---- web/src/utils/jurorRewardConfig.ts | 45 +++++++++++++++ web/src/utils/userLevelCalculation.ts | 15 +++++ web/src/utils/userLevelCalculation.tsx | 20 ------- 13 files changed, 166 insertions(+), 120 deletions(-) create mode 100644 web/src/hooks/queries/useTopUsersByCoherenceScore.ts create mode 100644 web/src/utils/jurorRewardConfig.ts create mode 100644 web/src/utils/userLevelCalculation.ts delete mode 100644 web/src/utils/userLevelCalculation.tsx diff --git a/subgraph/schema.graphql b/subgraph/schema.graphql index ad646bee1..68e69bbb1 100644 --- a/subgraph/schema.graphql +++ b/subgraph/schema.graphql @@ -72,6 +72,7 @@ type User @entity { totalResolvedDisputes: BigInt! totalDisputes: BigInt! totalCoherent: BigInt! + coherenceScore: BigInt! totalAppealingDisputes: BigInt! votes: [Vote!]! @derivedFrom(field: "juror") contributions: [Contribution!]! @derivedFrom(field: "contributor") diff --git a/subgraph/src/entities/User.ts b/subgraph/src/entities/User.ts index 918878bcb..1aa01e5fd 100644 --- a/subgraph/src/entities/User.ts +++ b/subgraph/src/entities/User.ts @@ -1,7 +1,25 @@ -import { BigInt } from "@graphprotocol/graph-ts"; +import { BigInt, BigDecimal } from "@graphprotocol/graph-ts"; import { User } from "../../generated/schema"; import { ONE, ZERO } from "../utils"; +function computeCoherenceScore(totalCoherent: BigInt, totalResolvedDisputes: BigInt): BigInt { + const smoothingFactor = BigInt.fromI32(10); + const shiftFactor = BigInt.fromI32(1000); + let denominator = totalResolvedDisputes.plus(smoothingFactor); + let coherencyRatio = totalCoherent.toBigDecimal().div(denominator.toBigDecimal()); + const coherencyScore = coherencyRatio.times(BigDecimal.fromString("100")); + const shiftedValue = coherencyScore.times(BigDecimal.fromString("1000")); + const shiftedBigInt = BigInt.fromString(shiftedValue.toString().split(".")[0]); + const halfShiftFactor = shiftFactor.div(BigInt.fromI32(2)); + const remainder = shiftedBigInt.mod(shiftFactor); + + if (remainder.ge(halfShiftFactor)) { + return shiftedBigInt.div(shiftFactor).plus(BigInt.fromI32(1)); + } else { + return shiftedBigInt.div(shiftFactor); + } +} + export function ensureUser(id: string): User { const user = User.load(id); @@ -24,6 +42,7 @@ export function createUserFromAddress(id: string): User { user.totalAppealingDisputes = ZERO; user.totalDisputes = ZERO; user.totalCoherent = ZERO; + user.coherenceScore = ZERO; user.save(); return user; @@ -61,5 +80,6 @@ export function resolveUserDispute(id: string, previousFeeAmount: BigInt, feeAmo user.totalCoherent = user.totalCoherent.plus(ONE); } user.activeDisputes = user.activeDisputes.minus(ONE); + user.coherenceScore = computeCoherenceScore(user.totalCoherent, user.totalResolvedDisputes); user.save(); } diff --git a/web/src/hooks/queries/useTopUsersByCoherenceScore.ts b/web/src/hooks/queries/useTopUsersByCoherenceScore.ts new file mode 100644 index 000000000..f09575ece --- /dev/null +++ b/web/src/hooks/queries/useTopUsersByCoherenceScore.ts @@ -0,0 +1,35 @@ +import { useQuery } from "@tanstack/react-query"; +import { graphql } from "src/graphql"; +import { graphqlQueryFnHelper } from "utils/graphqlQueryFnHelper"; +import { TopUsersByCoherenceScoreQuery } from "src/graphql/graphql"; +import { isUndefined } from "utils/index"; +export type { TopUsersByCoherenceScoreQuery }; + +const topUsersByCoherenceScoreQuery = graphql(` + query TopUsersByCoherenceScore($first: Int!, $orderBy: User_orderBy, $orderDirection: OrderDirection) { + users(first: $first, orderBy: $orderBy, orderDirection: $orderDirection) { + id + coherenceScore + totalCoherent + totalResolvedDisputes + } + } +`); + +export const useTopUsersByCoherenceScore = (first = 5) => { + const isEnabled = !isUndefined(first); + + return useQuery({ + queryKey: [`TopUsersByCoherenceScore${first}`], + enabled: isEnabled, + staleTime: Infinity, + queryFn: async () => + isEnabled + ? await graphqlQueryFnHelper(topUsersByCoherenceScoreQuery, { + first: first, + orderBy: "coherenceScore", + orderDirection: "desc", + }) + : undefined, + }); +}; diff --git a/web/src/hooks/queries/useUser.ts b/web/src/hooks/queries/useUser.ts index 94aa77828..6fa58a617 100644 --- a/web/src/hooks/queries/useUser.ts +++ b/web/src/hooks/queries/useUser.ts @@ -11,6 +11,7 @@ export const userFragment = graphql(` totalResolvedDisputes totalAppealingDisputes totalCoherent + coherenceScore tokens { court { id diff --git a/web/src/pages/Dashboard/JurorInfo/Coherency.tsx b/web/src/pages/Dashboard/JurorInfo/Coherency.tsx index e836754cf..6c9397b01 100644 --- a/web/src/pages/Dashboard/JurorInfo/Coherency.tsx +++ b/web/src/pages/Dashboard/JurorInfo/Coherency.tsx @@ -30,18 +30,14 @@ interface ICoherency { title: string; }; score: number; - totalCoherent: number; - totalResolvedDisputes: number; } -const Coherency: React.FC = ({ userLevelData, score, totalCoherent, totalResolvedDisputes }) => { +const Coherency: React.FC = ({ userLevelData, score }) => { return ( {userLevelData.title} - +