From faa026bc1b6a3276a84b03035b8af7894ff8405f Mon Sep 17 00:00:00 2001 From: Harman-singh-waraich Date: Thu, 9 Jan 2025 17:52:09 +0530 Subject: [PATCH 1/2] refactor(web): courts-stat --- web/src/pages/Courts/CourtDetails/Stats.tsx | 273 ------------------ .../CourtDetails/Stats/StatsContent.tsx | 122 ++++++++ .../pages/Courts/CourtDetails/Stats/index.tsx | 78 +++++ .../pages/Courts/CourtDetails/Stats/stats.ts | 116 ++++++++ web/src/pages/Courts/CourtDetails/index.tsx | 2 +- 5 files changed, 317 insertions(+), 274 deletions(-) delete mode 100644 web/src/pages/Courts/CourtDetails/Stats.tsx create mode 100644 web/src/pages/Courts/CourtDetails/Stats/StatsContent.tsx create mode 100644 web/src/pages/Courts/CourtDetails/Stats/index.tsx create mode 100644 web/src/pages/Courts/CourtDetails/Stats/stats.ts diff --git a/web/src/pages/Courts/CourtDetails/Stats.tsx b/web/src/pages/Courts/CourtDetails/Stats.tsx deleted file mode 100644 index 9f292cb6c..000000000 --- a/web/src/pages/Courts/CourtDetails/Stats.tsx +++ /dev/null @@ -1,273 +0,0 @@ -import React from "react"; -import styled, { css } from "styled-components"; -import { responsiveSize } from "styles/responsiveSize"; -import { landscapeStyle } from "styles/landscapeStyle"; - -import { useParams } from "react-router-dom"; -import { Accordion } from "@kleros/ui-components-library"; - -import EthereumIcon from "svgs/icons/ethereum.svg"; -import EthereumVoteIcon from "svgs/icons/ethereum-vote.svg"; -import BalanceIcon from "svgs/icons/law-balance.svg"; -import BalanceWithHourglassIcon from "svgs/icons/law-balance-hourglass.svg"; -import JurorIcon from "svgs/icons/user.svg"; -import MinStake from "svgs/icons/min-stake.svg"; -import PNKIcon from "svgs/icons/pnk.svg"; -import PNKRedistributedIcon from "svgs/icons/redistributed-pnk.svg"; -import VoteStake from "svgs/icons/vote-stake.svg"; -import ChartIcon from "svgs/icons/chart.svg"; - -import { CoinIds } from "consts/coingecko"; - -import { useCoinPrice } from "hooks/useCoinPrice"; -import { useCourtDetails, CourtDetailsQuery } from "queries/useCourtDetails"; - -import { calculateSubtextRender } from "utils/calculateSubtextRender"; -import { formatETH, formatPNK, formatUnitsWei, formatUSD } from "utils/format"; -import { isUndefined } from "utils/index"; - -import StatDisplay, { IStatDisplay } from "components/StatDisplay"; -import { StyledSkeleton } from "components/StyledSkeleton"; - -const StyledAccordion = styled(Accordion)` - > * > button { - padding: 12px 16px !important; - justify-content: unset; - } - //adds padding to body container - > * > div > div { - padding: 0 8px 8px; - } - [class*="accordion-item"] { - margin: 0; - } - - ${landscapeStyle( - () => css` - > * > div > div { - padding: 0 24px; - } - > * > button { - padding: 12px 24px !important; - } - ` - )} -`; - -const TimeDisplayContainer = styled.div` - display: flex; - flex-direction: row; - align-items: center; - gap: 8px; -`; - -const AllTimeContainer = styled(TimeDisplayContainer)` - padding: ${responsiveSize(12, 16)} 0; -`; - -const StyledAllTimeText = styled.p` - color: ${({ theme }) => theme.primaryText}; - margin: 0; - font-size: 14px; -`; - -const StyledChartIcon = styled(ChartIcon)` - path { - fill: ${({ theme }) => theme.primaryText}; - } -`; - -const StyledEthereumVoteIcon = styled(EthereumVoteIcon)` - height: 32px !important; -`; - -const StyledJurorIcon = styled(JurorIcon)` - height: 15px !important; -`; - -const StyledBalanceWithHourglassIcon = styled(BalanceWithHourglassIcon)` - height: 32px !important; -`; - -const AccordionContainer = styled.div` - display: flex; - flex-direction: column; - gap: 4px; -`; - -const StyledCard = styled.div` - display: flex; - flex-wrap: wrap; - gap: 20px 0; -`; - -interface IStat { - title: string; - coinId?: number; - getText: (data: CourtDetailsQuery["court"]) => string; - getSubtext?: (data: CourtDetailsQuery["court"], coinPrice?: number) => string; - color: IStatDisplay["color"]; - icon: React.FC>; -} - -const stats: IStat[] = [ - { - title: "Min Stake", - coinId: 0, - getText: (data) => `${formatPNK(data?.minStake)} PNK`, - getSubtext: (data, coinPrice) => formatUSD(Number(formatUnitsWei(data?.minStake)) * (coinPrice ?? 0)), - color: "blue", - icon: MinStake, - }, - { - title: "Vote Stake", - coinId: 0, - getText: (data) => { - const stake = BigInt((data?.minStake * data?.alpha) / 1e4); - return `${formatPNK(stake)} PNK`; - }, - getSubtext: (data, coinPrice) => { - const stake = BigInt((data?.minStake * data?.alpha) / 1e4); - return formatUSD(Number(formatUnitsWei(stake)) * (coinPrice ?? 0)); - }, - color: "blue", - icon: VoteStake, - }, - { - title: "Reward per Vote", - coinId: 1, - getText: (data) => { - const jurorReward = formatUnitsWei(data?.feeForJuror); - return `${jurorReward} ETH`; - }, - getSubtext: (data, coinPrice) => formatUSD(Number(formatUnitsWei(data?.feeForJuror)) * (coinPrice ?? 0)), - color: "blue", - icon: StyledEthereumVoteIcon, - }, - { - title: "PNK Staked", - coinId: 0, - getText: (data) => `${formatPNK(data?.stake)} PNK`, - getSubtext: (data, coinPrice) => formatUSD(Number(formatUnitsWei(data?.stake)) * (coinPrice ?? 0)), - color: "green", - icon: PNKIcon, - }, - { - title: "Active Jurors", - getText: (data) => data?.numberStakedJurors, - color: "green", - icon: StyledJurorIcon, - }, - { - title: "Cases", - getText: (data) => data?.numberDisputes, - color: "green", - icon: BalanceIcon, - }, - { - title: "In Progress", - getText: (data) => data?.numberDisputes - data?.numberClosedDisputes, - color: "green", - icon: StyledBalanceWithHourglassIcon, - }, - { - title: "ETH paid", - coinId: 1, - getText: (data) => `${formatETH(data?.paidETH)} ETH`, - getSubtext: (data, coinPrice) => formatUSD(Number(formatUnitsWei(data?.paidETH)) * (coinPrice ?? 0)), - color: "purple", - icon: EthereumIcon, - }, - { - title: "PNK redistributed", - coinId: 0, - getText: (data) => `${formatPNK(data?.paidPNK)} PNK`, - getSubtext: (data, coinPrice) => formatUSD(Number(formatUnitsWei(data?.paidPNK)) * (coinPrice ?? 0)), - color: "purple", - icon: PNKRedistributedIcon, - }, -]; - -const Stats = () => { - const { id } = useParams(); - const { data } = useCourtDetails(id); - const coinIds = [CoinIds.PNK, CoinIds.ETH]; - const { prices: pricesData } = useCoinPrice(coinIds); - - return ( - -
- - - Parameters - - - {stats.slice(0, 3).map(({ title, coinId, getText, getSubtext, color, icon }) => { - const coinPrice = !isUndefined(pricesData) ? pricesData[coinIds[coinId!]]?.price : undefined; - return ( - } - subtext={calculateSubtextRender(data?.court, getSubtext, coinPrice)} - isSmallDisplay={true} - /> - ); - })} - -
-
- - - Activity - - - {stats.slice(3, 7).map(({ title, coinId, getText, getSubtext, color, icon }) => { - const coinPrice = !isUndefined(pricesData) ? pricesData[coinIds[coinId!]]?.price : undefined; - return ( - } - subtext={calculateSubtextRender(data?.court, getSubtext, coinPrice)} - isSmallDisplay={true} - /> - ); - })} - -
-
- - - Total Rewards - - - {stats.slice(7, 9).map(({ title, coinId, getText, getSubtext, color, icon }) => { - const coinPrice = !isUndefined(pricesData) ? pricesData[coinIds[coinId!]]?.price : undefined; - return ( - } - subtext={calculateSubtextRender(data?.court, getSubtext, coinPrice)} - isSmallDisplay={true} - /> - ); - })} - -
- - ), - }, - ]} - >
- ); -}; - -export default Stats; diff --git a/web/src/pages/Courts/CourtDetails/Stats/StatsContent.tsx b/web/src/pages/Courts/CourtDetails/Stats/StatsContent.tsx new file mode 100644 index 000000000..d44fd8c7b --- /dev/null +++ b/web/src/pages/Courts/CourtDetails/Stats/StatsContent.tsx @@ -0,0 +1,122 @@ +import React from "react"; +import styled from "styled-components"; + +import ChartIcon from "svgs/icons/chart.svg"; + +import { calculateSubtextRender } from "utils/calculateSubtextRender"; +import { isUndefined } from "utils/index"; + +import { CourtDetailsQuery } from "queries/useCourtDetails"; + +import { responsiveSize } from "styles/responsiveSize"; + +import StatDisplay from "components/StatDisplay"; +import { StyledSkeleton } from "components/StyledSkeleton"; + +import { stats } from "./stats"; + +const TimeDisplayContainer = styled.div` + display: flex; + flex-direction: row; + align-items: center; + gap: 8px; +`; + +const AllTimeContainer = styled(TimeDisplayContainer)` + padding: ${responsiveSize(12, 16)} 0; +`; + +const StyledAllTimeText = styled.p` + color: ${({ theme }) => theme.primaryText}; + margin: 0; + font-size: 14px; +`; + +const StyledChartIcon = styled(ChartIcon)` + path { + fill: ${({ theme }) => theme.primaryText}; + } +`; + +const AccordionContainer = styled.div` + display: flex; + flex-direction: column; + gap: 4px; +`; + +const StyledCard = styled.div` + display: flex; + flex-wrap: wrap; + gap: 20px 0; +`; + +const StatsContent: React.FC<{ court: CourtDetailsQuery["court"]; pricesData: any; coinIds: string[] }> = ({ + court, + pricesData, + coinIds, +}) => ( + +
+ + + Parameters + + + {stats.slice(0, 3).map(({ title, coinId, getText, getSubtext, color, icon }) => { + const coinPrice = !isUndefined(pricesData) ? pricesData[coinIds[coinId!]]?.price : undefined; + return ( + } + subtext={calculateSubtextRender(court, getSubtext, coinPrice)} + isSmallDisplay={true} + /> + ); + })} + +
+
+ + + Activity + + + {stats.slice(3, 7).map(({ title, coinId, getText, getSubtext, color, icon }) => { + const coinPrice = !isUndefined(pricesData) ? pricesData[coinIds[coinId!]]?.price : undefined; + return ( + } + subtext={calculateSubtextRender(court, getSubtext, coinPrice)} + isSmallDisplay={true} + /> + ); + })} + +
+
+ + + Total Rewards + + + {stats.slice(7, 9).map(({ title, coinId, getText, getSubtext, color, icon }) => { + const coinPrice = !isUndefined(pricesData) ? pricesData[coinIds[coinId!]]?.price : undefined; + return ( + } + subtext={calculateSubtextRender(court, getSubtext, coinPrice)} + isSmallDisplay={true} + /> + ); + })} + +
+
+); + +export default StatsContent; diff --git a/web/src/pages/Courts/CourtDetails/Stats/index.tsx b/web/src/pages/Courts/CourtDetails/Stats/index.tsx new file mode 100644 index 000000000..75fb02a14 --- /dev/null +++ b/web/src/pages/Courts/CourtDetails/Stats/index.tsx @@ -0,0 +1,78 @@ +import React from "react"; +import styled, { css } from "styled-components"; + +import { useParams } from "react-router-dom"; + +import { Accordion } from "@kleros/ui-components-library"; + +import { CoinIds } from "consts/coingecko"; +import { useCoinPrice } from "hooks/useCoinPrice"; +import useIsDesktop from "hooks/useIsDesktop"; + +import { useCourtDetails } from "queries/useCourtDetails"; + +import { landscapeStyle } from "styles/landscapeStyle"; + +import StatsContent from "./StatsContent"; + +const Container = styled.div` + padding: 12px 24px; +`; + +const Header = styled.h3` + color: ${({ theme }) => theme.primaryText}; + font-weight: 600; + margin: 0; +`; + +const StyledAccordion = styled(Accordion)` + > * > button { + padding: 12px 16px !important; + justify-content: unset; + } + //adds padding to body container + > * > div > div { + padding: 0 8px 8px; + } + [class*="accordion-item"] { + margin: 0; + } + + ${landscapeStyle( + () => css` + > * > div > div { + padding: 0 24px; + } + > * > button { + padding: 12px 24px !important; + } + ` + )} +`; + +const Stats = () => { + const { id } = useParams(); + const { data } = useCourtDetails(id); + const coinIds = [CoinIds.PNK, CoinIds.ETH]; + const { prices: pricesData } = useCoinPrice(coinIds); + const isDesktop = useIsDesktop(); + + return isDesktop ? ( + +
Statistics
+ +
+ ) : ( + , + }, + ]} + > + ); +}; + +export default Stats; diff --git a/web/src/pages/Courts/CourtDetails/Stats/stats.ts b/web/src/pages/Courts/CourtDetails/Stats/stats.ts new file mode 100644 index 000000000..86c843000 --- /dev/null +++ b/web/src/pages/Courts/CourtDetails/Stats/stats.ts @@ -0,0 +1,116 @@ +import styled from "styled-components"; + +import EthereumVoteIcon from "svgs/icons/ethereum-vote.svg"; +import EthereumIcon from "svgs/icons/ethereum.svg"; +import BalanceWithHourglassIcon from "svgs/icons/law-balance-hourglass.svg"; +import BalanceIcon from "svgs/icons/law-balance.svg"; +import MinStake from "svgs/icons/min-stake.svg"; +import PNKIcon from "svgs/icons/pnk.svg"; +import PNKRedistributedIcon from "svgs/icons/redistributed-pnk.svg"; +import JurorIcon from "svgs/icons/user.svg"; +import VoteStake from "svgs/icons/vote-stake.svg"; + +import { formatETH, formatPNK, formatUnitsWei, formatUSD } from "utils/format"; + +import { CourtDetailsQuery } from "queries/useCourtDetails"; + +import { IStatDisplay } from "components/StatDisplay"; + +interface IStat { + title: string; + coinId?: number; + getText: (data: CourtDetailsQuery["court"]) => string; + getSubtext?: (data: CourtDetailsQuery["court"], coinPrice?: number) => string; + color: IStatDisplay["color"]; + icon: React.FC>; +} + +const StyledEthereumVoteIcon = styled(EthereumVoteIcon)` + height: 32px !important; +`; + +const StyledJurorIcon = styled(JurorIcon)` + height: 15px !important; +`; + +const StyledBalanceWithHourglassIcon = styled(BalanceWithHourglassIcon)` + height: 32px !important; +`; + +export const stats: IStat[] = [ + { + title: "Min Stake", + coinId: 0, + getText: (data) => `${formatPNK(data?.minStake)} PNK`, + getSubtext: (data, coinPrice) => formatUSD(Number(formatUnitsWei(data?.minStake)) * (coinPrice ?? 0)), + color: "blue", + icon: MinStake, + }, + { + title: "Vote Stake", + coinId: 0, + getText: (data) => { + const stake = BigInt((data?.minStake * data?.alpha) / 1e4); + return `${formatPNK(stake)} PNK`; + }, + getSubtext: (data, coinPrice) => { + const stake = BigInt((data?.minStake * data?.alpha) / 1e4); + return formatUSD(Number(formatUnitsWei(stake)) * (coinPrice ?? 0)); + }, + color: "blue", + icon: VoteStake, + }, + { + title: "Reward per Vote", + coinId: 1, + getText: (data) => { + const jurorReward = formatUnitsWei(data?.feeForJuror); + return `${jurorReward} ETH`; + }, + getSubtext: (data, coinPrice) => formatUSD(Number(formatUnitsWei(data?.feeForJuror)) * (coinPrice ?? 0)), + color: "blue", + icon: StyledEthereumVoteIcon, + }, + { + title: "PNK Staked", + coinId: 0, + getText: (data) => `${formatPNK(data?.stake)} PNK`, + getSubtext: (data, coinPrice) => formatUSD(Number(formatUnitsWei(data?.stake)) * (coinPrice ?? 0)), + color: "green", + icon: PNKIcon, + }, + { + title: "Active Jurors", + getText: (data) => data?.numberStakedJurors, + color: "green", + icon: StyledJurorIcon, + }, + { + title: "Cases", + getText: (data) => data?.numberDisputes, + color: "green", + icon: BalanceIcon, + }, + { + title: "In Progress", + getText: (data) => data?.numberDisputes - data?.numberClosedDisputes, + color: "green", + icon: StyledBalanceWithHourglassIcon, + }, + { + title: "ETH paid", + coinId: 1, + getText: (data) => `${formatETH(data?.paidETH)} ETH`, + getSubtext: (data, coinPrice) => formatUSD(Number(formatUnitsWei(data?.paidETH)) * (coinPrice ?? 0)), + color: "purple", + icon: EthereumIcon, + }, + { + title: "PNK redistributed", + coinId: 0, + getText: (data) => `${formatPNK(data?.paidPNK)} PNK`, + getSubtext: (data, coinPrice) => formatUSD(Number(formatUnitsWei(data?.paidPNK)) * (coinPrice ?? 0)), + color: "purple", + icon: PNKRedistributedIcon, + }, +]; diff --git a/web/src/pages/Courts/CourtDetails/index.tsx b/web/src/pages/Courts/CourtDetails/index.tsx index a72802353..76f285677 100644 --- a/web/src/pages/Courts/CourtDetails/index.tsx +++ b/web/src/pages/Courts/CourtDetails/index.tsx @@ -15,12 +15,12 @@ import { landscapeStyle } from "styles/landscapeStyle"; import { responsiveSize } from "styles/responsiveSize"; import ClaimPnkButton from "components/ClaimPnkButton"; +import { Divider } from "components/Divider"; import HowItWorks from "components/HowItWorks"; import LatestCases from "components/LatestCases"; import Staking from "components/Popup/MiniGuides/Staking"; import ScrollTop from "components/ScrollTop"; import { StyledSkeleton } from "components/StyledSkeleton"; -import { Divider } from "components/Divider"; import Description from "./Description"; import StakePanel from "./StakePanel"; From 721d0298bdb2a305393b7312f01e8a8c55927cf8 Mon Sep 17 00:00:00 2001 From: Harman-singh-waraich Date: Thu, 9 Jan 2025 18:04:22 +0530 Subject: [PATCH 2/2] refactor(web): rabbit-feedback --- web/src/hooks/useCoinPrice.tsx | 6 +++++- web/src/pages/Courts/CourtDetails/Stats/StatsContent.tsx | 3 ++- web/src/pages/Courts/CourtDetails/Stats/stats.ts | 2 +- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/web/src/hooks/useCoinPrice.tsx b/web/src/hooks/useCoinPrice.tsx index edd27e2e9..1fc6aff61 100644 --- a/web/src/hooks/useCoinPrice.tsx +++ b/web/src/hooks/useCoinPrice.tsx @@ -6,10 +6,14 @@ const fetchCoinPrices = async (...coinIds) => { return data.coins; }; +export type Prices = { + [coinId: string]: { price: number }; +}; + export const useCoinPrice = (coinIds: string[]) => { const isEnabled = coinIds !== undefined; - const { data: prices, isError } = useQuery({ + const { data: prices, isError } = useQuery({ queryKey: [`coinPrice${coinIds}`], enabled: isEnabled, queryFn: async () => fetchCoinPrices(coinIds), diff --git a/web/src/pages/Courts/CourtDetails/Stats/StatsContent.tsx b/web/src/pages/Courts/CourtDetails/Stats/StatsContent.tsx index d44fd8c7b..bb6653ab4 100644 --- a/web/src/pages/Courts/CourtDetails/Stats/StatsContent.tsx +++ b/web/src/pages/Courts/CourtDetails/Stats/StatsContent.tsx @@ -3,6 +3,7 @@ import styled from "styled-components"; import ChartIcon from "svgs/icons/chart.svg"; +import { Prices } from "hooks/useCoinPrice"; import { calculateSubtextRender } from "utils/calculateSubtextRender"; import { isUndefined } from "utils/index"; @@ -50,7 +51,7 @@ const StyledCard = styled.div` gap: 20px 0; `; -const StatsContent: React.FC<{ court: CourtDetailsQuery["court"]; pricesData: any; coinIds: string[] }> = ({ +const StatsContent: React.FC<{ court: CourtDetailsQuery["court"]; pricesData?: Prices; coinIds: string[] }> = ({ court, pricesData, coinIds, diff --git a/web/src/pages/Courts/CourtDetails/Stats/stats.ts b/web/src/pages/Courts/CourtDetails/Stats/stats.ts index 86c843000..1dd645ad3 100644 --- a/web/src/pages/Courts/CourtDetails/Stats/stats.ts +++ b/web/src/pages/Courts/CourtDetails/Stats/stats.ts @@ -54,7 +54,7 @@ export const stats: IStat[] = [ return `${formatPNK(stake)} PNK`; }, getSubtext: (data, coinPrice) => { - const stake = BigInt((data?.minStake * data?.alpha) / 1e4); + const stake = (BigInt(data?.minStake) * BigInt(data?.alpha)) / BigInt(1e4); return formatUSD(Number(formatUnitsWei(stake)) * (coinPrice ?? 0)); }, color: "blue",