diff --git a/web/src/assets/svgs/footer/secured-by-kleros.svg b/web/src/assets/svgs/footer/secured-by-kleros.svg index eac640c7a..cceaf5c05 100644 --- a/web/src/assets/svgs/footer/secured-by-kleros.svg +++ b/web/src/assets/svgs/footer/secured-by-kleros.svg @@ -1,12 +1,10 @@ - - - - - + + + - - + + diff --git a/web/src/assets/svgs/header/kleros-court.svg b/web/src/assets/svgs/header/kleros-court.svg index dc698e7ae..271675f2e 100644 --- a/web/src/assets/svgs/header/kleros-court.svg +++ b/web/src/assets/svgs/header/kleros-court.svg @@ -1,11 +1,3 @@ - - - - - - - - - - + + diff --git a/web/src/components/DisputeView/CardLabels/Label.tsx b/web/src/components/DisputeView/CardLabels/Label.tsx index fab0ae148..766f0d8fe 100644 --- a/web/src/components/DisputeView/CardLabels/Label.tsx +++ b/web/src/components/DisputeView/CardLabels/Label.tsx @@ -17,7 +17,7 @@ const LabelContainer = styled.div<{ backgroundColor: string }>` width: max-content; padding: 4px 8px; align-items: center; - gap: 10px; + gap: 8px; border-radius: 300px; background-color: ${({ backgroundColor }) => backgroundColor}; `; diff --git a/web/src/components/StatDisplay.tsx b/web/src/components/StatDisplay.tsx index bf0d2c6b9..4b8b705cc 100644 --- a/web/src/components/StatDisplay.tsx +++ b/web/src/components/StatDisplay.tsx @@ -1,39 +1,63 @@ import React from "react"; import styled, { useTheme, css } from "styled-components"; -import { landscapeStyle } from "styles/landscapeStyle"; -import { responsiveSize } from "styles/responsiveSize"; - -const Container = styled.div` +const Container = styled.div<{ isSmallDisplay: boolean }>` display: flex; - max-width: 196px; align-items: center; gap: 8px; - - ${landscapeStyle( - () => css` - margin-bottom: ${responsiveSize(16, 30)}; - ` - )} + ${({ isSmallDisplay }) => + isSmallDisplay + ? css` + width: 151px; + ` + : css` + max-width: 196px; + `} `; -const SVGContainer = styled.div<{ iconColor: string; backgroundColor: string }>` - height: 48px; - width: 48px; +const SVGContainer = styled.div<{ iconColor: string; backgroundColor: string; isSmallDisplay: boolean }>` border-radius: 50%; - background-color: ${({ backgroundColor }) => backgroundColor}; display: flex; align-items: center; justify-content: center; + background-color: ${({ backgroundColor }) => backgroundColor}; svg { fill: ${({ iconColor }) => iconColor}; } + + ${({ isSmallDisplay }) => + isSmallDisplay + ? css` + height: 32px; + width: 32px; + svg { + height: 20px; + } + ` + : css` + height: 48px; + width: 48px; + `} `; -const TextContainer = styled.div` - h1 { - margin: 0; - } +const TextContainer = styled.div<{ isSmallDisplay: boolean }>` + display: flex; + flex-direction: column; + gap: ${({ isSmallDisplay }) => (isSmallDisplay ? "3px" : "8px")}; +`; + +const StyledTitle = styled.label` + font-size: 14px; +`; + +const StyledValue = styled.label<{ isSmallDisplay: boolean }>` + font-size: ${({ isSmallDisplay }) => (isSmallDisplay ? "16px" : "24px")}; + font-weight: 600; + color: ${({ theme }) => theme.primaryText}; +`; + +const StyledUSDValue = styled.label<{ isSmallDisplay: boolean }>` + font-size: ${({ isSmallDisplay }) => (isSmallDisplay ? "12px" : "14px")}; `; const createPair = (iconColor: string, backgroundColor: string) => ({ @@ -47,9 +71,18 @@ export interface IStatDisplay { subtext?: string | React.ReactNode; icon: React.FunctionComponent>; color: "red" | "orange" | "green" | "blue" | "purple"; + isSmallDisplay?: boolean; } -const StatDisplay: React.FC = ({ title, text, subtext, icon: Icon, color, ...props }) => { +const StatDisplay: React.FC = ({ + title, + text, + subtext, + icon: Icon, + color, + isSmallDisplay = false, + ...props +}) => { const theme = useTheme(); const COLORS = { red: createPair(theme.error, theme.errorLight), @@ -60,12 +93,12 @@ const StatDisplay: React.FC = ({ title, text, subtext, icon: Icon, }; return ( - - {} - - -

{text}

- + + {} + + {title} + {text} + {subtext} ); diff --git a/web/src/components/Verdict/FinalDecision.tsx b/web/src/components/Verdict/FinalDecision.tsx index b70ab2dbe..b4b5bd21c 100644 --- a/web/src/components/Verdict/FinalDecision.tsx +++ b/web/src/components/Verdict/FinalDecision.tsx @@ -15,13 +15,14 @@ import { usePopulatedDisputeData } from "hooks/queries/usePopulatedDisputeData"; import { useVotingHistory } from "hooks/queries/useVotingHistory"; import { useVotingContext } from "hooks/useVotingContext"; import { getLocalRounds } from "utils/getLocalRounds"; +import { isUndefined } from "utils/index"; import { useDisputeDetailsQuery } from "queries/useDisputeDetailsQuery"; import { responsiveSize } from "styles/responsiveSize"; +import RulingAndRewardsIndicators from "./RulingAndRewardsIndicators"; import AnswerDisplay from "./Answer"; -import VerdictBanner from "./VerdictBanner"; import { Divider } from "../Divider"; import { StyledArrowLink } from "../StyledArrowLink"; @@ -90,6 +91,8 @@ const FinalDecision: React.FC = ({ arbitrable }) => { }); const currentRuling = Number(currentRulingArray?.[0]); const answer = populatedDisputeData?.answers?.[currentRuling! - 1]; + const rounds = votingHistory?.dispute?.rounds; + const jurorRewardsDispersed = useMemo(() => Boolean(rounds?.every((round) => round.jurorRewardsDispersed)), [rounds]); const buttonText = useMemo(() => { if (!wasDrawn || isDisconnected) return "Check how the jury voted"; if (isCommitPeriod && !commited) return "Commit your vote"; @@ -101,7 +104,12 @@ const FinalDecision: React.FC = ({ arbitrable }) => { return ( - + {!isUndefined(Boolean(disputeDetails?.dispute?.ruled)) || jurorRewardsDispersed ? ( + + ) : null} {ruled && ( The jury decided in favor of: diff --git a/web/src/pages/Cases/CaseDetails/Voting/RulingAndRewardsIndicators.tsx b/web/src/components/Verdict/RulingAndRewardsIndicators.tsx similarity index 76% rename from web/src/pages/Cases/CaseDetails/Voting/RulingAndRewardsIndicators.tsx rename to web/src/components/Verdict/RulingAndRewardsIndicators.tsx index eefbea54a..dc13f198f 100644 --- a/web/src/pages/Cases/CaseDetails/Voting/RulingAndRewardsIndicators.tsx +++ b/web/src/components/Verdict/RulingAndRewardsIndicators.tsx @@ -2,6 +2,7 @@ import React from "react"; import styled from "styled-components"; import CheckCircle from "svgs/icons/check-circle-outline.svg"; +import Hourglass from "svgs/icons/hourglass.svg"; import Coins from "svgs/icons/pile-coins.svg"; import Label from "components/DisputeView/CardLabels/Label"; @@ -19,7 +20,11 @@ interface IRulingAndRewardsIndicators { const RulingAndRewardsIndicators: React.FC = ({ jurorRewardsDispersed, ruled }) => ( - {ruled ? ); diff --git a/web/src/components/Verdict/VerdictBanner.tsx b/web/src/components/Verdict/VerdictBanner.tsx deleted file mode 100644 index 3c5bf544f..000000000 --- a/web/src/components/Verdict/VerdictBanner.tsx +++ /dev/null @@ -1,58 +0,0 @@ -import React from "react"; -import styled from "styled-components"; - -import ClosedCaseIcon from "svgs/icons/check-circle-outline.svg"; -import HourglassIcon from "svgs/icons/hourglass.svg"; - -const BannerContainer = styled.div` - display: flex; - gap: 8px; - align-items: center; - svg { - width: 16px; - height: 16px; - } -`; - -const VerdictTag = styled.small<{ ruled: boolean }>` - font-weight: 400; - line-height: 19px; - color: ${({ theme, ruled }) => (ruled ? theme.success : theme.primaryText)}; -`; - -const StyledHourglassIcon = styled(HourglassIcon)` - fill: ${({ theme }) => theme.primaryText}; -`; - -interface IVerdictIcon { - ruled: boolean; -} - -const VerdictIcon: React.FC = ({ ruled }) => { - return ruled ? : ; -}; - -interface IVerdictText { - ruled: boolean; -} - -const VerdictText: React.FC = ({ ruled }) => { - return ruled ? <>Case closed : <>Case ongoing; -}; - -interface IVerdictBanner { - ruled: boolean; -} - -const VerdictBanner: React.FC = ({ ruled }) => { - return ( - - - - - - - ); -}; - -export default VerdictBanner; diff --git a/web/src/hooks/queries/useHomePageBlockQuery.ts b/web/src/hooks/queries/useHomePageBlockQuery.ts index 0e66b195e..6bf142549 100644 --- a/web/src/hooks/queries/useHomePageBlockQuery.ts +++ b/web/src/hooks/queries/useHomePageBlockQuery.ts @@ -128,8 +128,8 @@ const processData = (data: HomePageBlockQuery, allTime: boolean) => { }; const addTreeValues = (court: Court): CourtWithTree => { - const votesPerPnk = Number(court.numberVotes) / (Number(court.effectiveStake) / 1e18); - const disputesPerPnk = Number(court.numberDisputes) / (Number(court.effectiveStake) / 1e18); + const votesPerPnk = Number(court.numberVotes) / (Number(court.effectiveStake) / 1e18) || 0; + const disputesPerPnk = Number(court.numberDisputes) / (Number(court.effectiveStake) / 1e18) || 0; const expectedRewardPerPnk = votesPerPnk * (Number(court.feeForJuror) / 1e18); return { ...court, @@ -154,8 +154,8 @@ const addTreeValuesWithDiff = (presentCourt: Court, pastCourt: Court): CourtWith const diffNumberVotes = presentCourtWithTree.numberVotes - pastCourtWithTree.numberVotes; const diffNumberDisputes = presentCourtWithTree.numberDisputes - pastCourtWithTree.numberDisputes; const avgEffectiveStake = (presentCourtWithTree.effectiveStake + pastCourtWithTree.effectiveStake) / 2n; - const votesPerPnk = diffNumberVotes / (Number(avgEffectiveStake) / 1e18); - const disputesPerPnk = diffNumberDisputes / (Number(avgEffectiveStake) / 1e18); + const votesPerPnk = diffNumberVotes / (Number(avgEffectiveStake) / 1e18) || 0; + const disputesPerPnk = diffNumberDisputes / (Number(avgEffectiveStake) / 1e18) || 0; const expectedRewardPerPnk = votesPerPnk * (Number(presentCourt.feeForJuror) / 1e18); return { ...presentCourt, diff --git a/web/src/layout/Header/navbar/Explore.tsx b/web/src/layout/Header/navbar/Explore.tsx index fb5209ced..b28482eee 100644 --- a/web/src/layout/Header/navbar/Explore.tsx +++ b/web/src/layout/Header/navbar/Explore.tsx @@ -8,7 +8,6 @@ import { useOpenContext } from "../MobileHeader"; const Container = styled.div` display: flex; - gap: 0; flex-direction: column; ${landscapeStyle( diff --git a/web/src/pages/Cases/CaseDetails/Appeal/AppealHistory.tsx b/web/src/pages/Cases/CaseDetails/Appeal/AppealHistory.tsx index c8fa48cd2..ce029a5d6 100644 --- a/web/src/pages/Cases/CaseDetails/Appeal/AppealHistory.tsx +++ b/web/src/pages/Cases/CaseDetails/Appeal/AppealHistory.tsx @@ -11,9 +11,9 @@ import OptionCard from "./OptionCard"; import { AppealHeader, StyledTitle } from "."; const OptionsContainer = styled.div` - display: flex; - flex-wrap: wrap; - gap: 24px; + display: grid; + grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); + gap: 16px; margin-top: 12px; `; diff --git a/web/src/pages/Cases/CaseDetails/Appeal/Classic/Options/StageOne.tsx b/web/src/pages/Cases/CaseDetails/Appeal/Classic/Options/StageOne.tsx index 46d967432..3ef565e22 100644 --- a/web/src/pages/Cases/CaseDetails/Appeal/Classic/Options/StageOne.tsx +++ b/web/src/pages/Cases/CaseDetails/Appeal/Classic/Options/StageOne.tsx @@ -18,9 +18,9 @@ const Container = styled.div` `; const OptionsContainer = styled.div` - display: flex; - flex-wrap: wrap; - gap: 24px; + display: grid; + grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); + gap: 16px; margin-top: 12px; `; diff --git a/web/src/pages/Cases/CaseDetails/Appeal/Classic/Options/StageTwo.tsx b/web/src/pages/Cases/CaseDetails/Appeal/Classic/Options/StageTwo.tsx index 08bad3d58..f14077533 100644 --- a/web/src/pages/Cases/CaseDetails/Appeal/Classic/Options/StageTwo.tsx +++ b/web/src/pages/Cases/CaseDetails/Appeal/Classic/Options/StageTwo.tsx @@ -20,9 +20,9 @@ const Container = styled.div` `; const OptionsContainer = styled.div` - display: flex; - flex-wrap: wrap; - gap: 24px; + display: grid; + grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); + gap: 16px; margin-top: 12px; `; diff --git a/web/src/pages/Cases/CaseDetails/Voting/VotingHistory.tsx b/web/src/pages/Cases/CaseDetails/Voting/VotingHistory.tsx index bb51117bf..7beedf04c 100644 --- a/web/src/pages/Cases/CaseDetails/Voting/VotingHistory.tsx +++ b/web/src/pages/Cases/CaseDetails/Voting/VotingHistory.tsx @@ -22,7 +22,6 @@ import HowItWorks from "components/HowItWorks"; import BinaryVoting from "components/Popup/MiniGuides/BinaryVoting"; import PendingVotesBox from "./PendingVotesBox"; -import RulingAndRewardsIndicators from "./RulingAndRewardsIndicators"; import VotesAccordion from "./VotesDetails"; const Container = styled.div` @@ -80,16 +79,8 @@ const VotingHistory: React.FC<{ arbitrable?: `0x${string}`; isQuestion: boolean [votingHistory, currentTab] ); - const jurorRewardsDispersed = useMemo(() => Boolean(rounds?.every((round) => round.jurorRewardsDispersed)), [rounds]); - return ( - {Boolean(disputeData?.dispute?.ruled) || jurorRewardsDispersed ? ( - - ) : null}
Voting History { const { id } = useParams(); const { data: policy } = useCourtPolicy(id); const navigate = useNavigate(); - const currentPathName = useLocation().pathname.split("/").at(-1); - const [currentTab, setCurrentTab] = useState(TABS.findIndex(({ path }) => path === currentPathName)); - useEffect(() => setCurrentTab(TABS.findIndex(({ path }) => path === currentPathName)), [currentPathName]); + const location = useLocation(); + const currentPathName = location.pathname.split("/").at(-1); const filteredTabs = TABS.filter(({ isVisible }) => isVisible(policy)); + const currentTab = TABS.findIndex(({ path }) => path === currentPathName); + + const handleTabChange = (index: number) => { + navigate(TABS[index].path); + }; + + useEffect(() => { + if (currentPathName && !filteredTabs.map((t) => t.path).includes(currentPathName) && filteredTabs.length > 0) { + navigate(filteredTabs[0].path, { replace: true }); + } + }, [policy, currentPathName, filteredTabs, navigate]); return ( - - { - setCurrentTab(n); - navigate(TABS[n].path); - }} - /> - - - - {policy?.requiredSkills}

} /> - 0 ? filteredTabs[0].path : ""} replace /> - ) - } - /> - 0 ? filteredTabs[0].path : ""} replace />} /> -
-
-
+ <> + {policy ? ( + + + + + + + + 0 ? filteredTabs[0].path : ""} replace />} /> + + + + ) : null} + ); }; const formatMarkdown = (markdown?: string) => - markdown ? ( - {typeof markdown === "string" ? markdown.replace(/\n/g, " \n") : markdown} - ) : ( - - ); + markdown ? {markdown.replace(/\n/g, " \n")} : ; export default Description; diff --git a/web/src/pages/Courts/CourtDetails/StakePanel/InputDisplay.tsx b/web/src/pages/Courts/CourtDetails/StakePanel/InputDisplay.tsx index 6a28e9685..1ea779a74 100644 --- a/web/src/pages/Courts/CourtDetails/StakePanel/InputDisplay.tsx +++ b/web/src/pages/Courts/CourtDetails/StakePanel/InputDisplay.tsx @@ -26,12 +26,6 @@ const StyledField = styled(NumberInputField)` const LabelArea = styled.div` display: flex; justify-content: space-between; - - ${landscapeStyle( - () => css` - width: 92%; - ` - )} `; const StyledLabel = styled.label` @@ -45,12 +39,6 @@ const InputArea = styled.div` align-items: center; gap: 12px; width: 100%; - - ${landscapeStyle( - () => css` - width: 92%; - ` - )} `; const InputFieldAndButton = styled.div` diff --git a/web/src/pages/Courts/CourtDetails/StakePanel/Simulator/index.tsx b/web/src/pages/Courts/CourtDetails/StakePanel/Simulator/index.tsx index 134045dbe..8ebc79bcd 100644 --- a/web/src/pages/Courts/CourtDetails/StakePanel/Simulator/index.tsx +++ b/web/src/pages/Courts/CourtDetails/StakePanel/Simulator/index.tsx @@ -31,7 +31,6 @@ import { Divider } from "components/Divider"; const Container = styled.div` display: flex; flex-direction: column; - max-width: 480px; background-color: ${({ theme }) => theme.lightBlue}; box-shadow: 0px 4px 12px rgba(0, 0, 0, 0.1); padding: 20px; diff --git a/web/src/pages/Courts/CourtDetails/StakePanel/index.tsx b/web/src/pages/Courts/CourtDetails/StakePanel/index.tsx index 2171058a2..11e6696cd 100644 --- a/web/src/pages/Courts/CourtDetails/StakePanel/index.tsx +++ b/web/src/pages/Courts/CourtDetails/StakePanel/index.tsx @@ -13,15 +13,13 @@ import Simulator from "./Simulator"; const Container = styled.div` position: relative; - width: 100%; display: flex; flex-direction: column; gap: 28px; ${landscapeStyle( () => css` - flex-direction: row; - justify-content: space-between; + flex-direction: column; ` )}; `; @@ -29,12 +27,6 @@ const Container = styled.div` const LeftArea = styled.div` display: flex; flex-direction: column; - - ${landscapeStyle( - () => css` - width: 50%; - ` - )}; `; const TagArea = styled.div` diff --git a/web/src/pages/Courts/CourtDetails/Stats.tsx b/web/src/pages/Courts/CourtDetails/Stats.tsx index 15941591f..f84dcce4d 100644 --- a/web/src/pages/Courts/CourtDetails/Stats.tsx +++ b/web/src/pages/Courts/CourtDetails/Stats.tsx @@ -1,41 +1,31 @@ -import React, { useMemo, useState } from "react"; -import styled, { css } from "styled-components"; -import { landscapeStyle } from "styles/landscapeStyle"; +import React from "react"; +import styled from "styled-components"; import { responsiveSize } from "styles/responsiveSize"; import { useParams } from "react-router-dom"; -import { Accordion, DropdownSelect } from "@kleros/ui-components-library"; +import { Accordion } from "@kleros/ui-components-library"; import EthereumIcon from "svgs/icons/ethereum.svg"; import BalanceIcon from "svgs/icons/law-balance.svg"; import MinStake from "svgs/icons/min-stake.svg"; -import VotesPerPNKIcon from "svgs/icons/votes-per-pnk.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 PNKUSDIcon from "svgs/icons/pnk-usd.svg"; -import PNKETHIcon from "svgs/icons/pnk-eth.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 { useHomePageExtraStats } from "queries/useHomePageExtraStats"; import { calculateSubtextRender } from "utils/calculateSubtextRender"; import { formatETH, formatPNK, formatUnitsWei, formatUSD } from "utils/format"; import { isUndefined } from "utils/index"; -import { beautifyStatNumber, unbeautifyStatNumber } from "utils/beautifyStatNumber"; import StatDisplay, { IStatDisplay } from "components/StatDisplay"; import { StyledSkeleton } from "components/StyledSkeleton"; -import WithHelpTooltip from "components/WithHelpTooltip"; -import Info from "./Info"; const StyledAccordion = styled(Accordion)` - width: 100%; - margin-top: ${responsiveSize(24, 32)}; > * > button { justify-content: unset; background-color: ${({ theme }) => theme.whiteBackground} !important; @@ -49,7 +39,10 @@ const StyledAccordion = styled(Accordion)` } //adds padding to body container > * > div > div { - padding: 0; + padding: 0 24px; + } + [class*="accordion-item"] { + margin: 0; } `; @@ -61,13 +54,7 @@ const TimeDisplayContainer = styled.div` `; const AllTimeContainer = styled(TimeDisplayContainer)` - padding-top: ${responsiveSize(12, 20)}; -`; - -const TimeSelectorContainer = styled(TimeDisplayContainer)` - padding-top: 12px; - padding-bottom: 12px; - flex-wrap: wrap; + padding: ${responsiveSize(12, 16)} 0; `; const StyledAllTimeText = styled.p` @@ -83,30 +70,16 @@ const StyledChartIcon = styled(ChartIcon)` } `; -const StyledCard = styled.div` - width: auto; - height: fit-content; - display: grid; - gap: 32px; - grid-template-columns: repeat(auto-fit, minmax(156px, 1fr)); - padding-top: ${responsiveSize(28, 32)}; - padding-bottom: ${responsiveSize(20, 0)}; - - ${landscapeStyle( - () => css` - gap: 16px; - ` - )} +const AccordionContainer = styled.div` + display: flex; + flex-direction: column; + gap: 4px; `; -const StyledDropdownSelect = styled(DropdownSelect)` - margin-right: 16px; - small { - color: ${({ theme }) => theme.primaryText}; - } - svg { - fill: ${({ theme }) => theme.primaryText}; - } +const StyledCard = styled.div` + display: flex; + flex-wrap: wrap; + gap: 20px 0; `; interface IStat { @@ -122,9 +95,9 @@ const stats: IStat[] = [ { title: "Min Stake", coinId: 0, - getText: (data) => formatPNK(data?.minStake), + getText: (data) => `${formatPNK(data?.minStake)} PNK`, getSubtext: (data, coinPrice) => formatUSD(Number(formatUnitsWei(data?.minStake)) * (coinPrice ?? 0)), - color: "purple", + color: "blue", icon: MinStake, }, { @@ -132,13 +105,13 @@ const stats: IStat[] = [ coinId: 0, getText: (data) => { const stake = BigInt((data?.minStake * data?.alpha) / 1e4); - return formatPNK(stake); + return `${formatPNK(stake)} PNK`; }, getSubtext: (data, coinPrice) => { const stake = BigInt((data?.minStake * data?.alpha) / 1e4); return formatUSD(Number(formatUnitsWei(stake)) * (coinPrice ?? 0)); }, - color: "purple", + color: "blue", icon: VoteStake, }, { @@ -146,156 +119,62 @@ const stats: IStat[] = [ coinId: 1, getText: (data) => { const jurorReward = formatUnitsWei(data?.feeForJuror); - return jurorReward; + return `${jurorReward} ETH`; }, getSubtext: (data, coinPrice) => formatUSD(Number(formatUnitsWei(data?.feeForJuror)) * (coinPrice ?? 0)), - color: "purple", + color: "blue", icon: EthereumIcon, }, - { - title: "Active Jurors", - getText: (data) => data?.numberStakedJurors, - color: "purple", - icon: PNKRedistributedIcon, - }, { title: "PNK Staked", coinId: 0, - getText: (data) => formatPNK(data?.stake), + getText: (data) => `${formatPNK(data?.stake)} PNK`, getSubtext: (data, coinPrice) => formatUSD(Number(formatUnitsWei(data?.stake)) * (coinPrice ?? 0)), - color: "purple", + color: "green", icon: PNKIcon, }, + { + title: "Active Jurors", + getText: (data) => data?.numberStakedJurors, + color: "green", + icon: PNKRedistributedIcon, + }, { title: "Cases", getText: (data) => data?.numberDisputes, - color: "orange", + color: "green", icon: BalanceIcon, }, { title: "In Progress", getText: (data) => data?.numberDisputes - data?.numberClosedDisputes, - color: "orange", + color: "green", icon: BalanceIcon, }, { title: "Total ETH paid", coinId: 1, - getText: (data) => formatETH(data?.paidETH), + getText: (data) => `${formatETH(data?.paidETH)} ETH`, getSubtext: (data, coinPrice) => formatUSD(Number(formatUnitsWei(data?.paidETH)) * (coinPrice ?? 0)), - color: "blue", + color: "purple", icon: EthereumIcon, }, { title: "PNK redistributed", coinId: 0, - getText: (data) => formatPNK(data?.paidPNK), + getText: (data) => `${formatPNK(data?.paidPNK)} PNK`, getSubtext: (data, coinPrice) => formatUSD(Number(formatUnitsWei(data?.paidPNK)) * (coinPrice ?? 0)), color: "purple", icon: PNKRedistributedIcon, }, ]; -interface ITimeframedStatData { - treeExpectedRewardPerPnk: number; - treeVotesPerPnk: number; - treeDisputesPerPnk: number; -} - -interface ITimeframedStat { - title: string | React.ReactNode; - coinId?: number; - getText: (data: ITimeframedStatData) => string; - getSubtext?: (data: CourtDetailsQuery["court"], coinPrice?: number) => string; - color: IStatDisplay["color"]; - icon: React.FC>; -} - -const timeRanges = [ - { value: 30, text: "Last 30 days" }, - { value: 90, text: "Last 90 days" }, - { value: 180, text: "Last 180 days" }, - { value: "allTime", text: "All Time" }, -]; - const Stats = () => { const { id } = useParams(); const { data } = useCourtDetails(id); - const [selectedRange, setSelectedRange] = useState(timeRanges[0].value); - const timeframedCourtData = useHomePageExtraStats(selectedRange); const coinIds = [CoinIds.PNK, CoinIds.ETH]; const { prices: pricesData } = useCoinPrice(coinIds); - const foundCourt = useMemo(() => { - return timeframedCourtData?.data?.courts?.find((c) => c.id === id); - }, [timeframedCourtData, id]); - - const handleTimeRangeChange = (value: string | number) => { - setSelectedRange(value); - }; - - const timeframedStats: ITimeframedStat[] = [ - { - title: ( - - PNK for 1 Vote - - ), - coinId: 0, - getText: (data) => beautifyStatNumber(data?.treeVotesPerPnk, true), - getSubtext: (data, coinPrice) => - formatUSD(unbeautifyStatNumber(beautifyStatNumber(data?.treeVotesPerPnk, true)) * (coinPrice ?? 0)), - color: "orange", - icon: VotesPerPNKIcon, - }, - { - title: ( - - PNK for 1 USD - - ), - coinId: 0, - getText: (data) => { - const treeExpectedRewardPerPnk = data?.treeExpectedRewardPerPnk; - const ethPriceUSD = pricesData ? pricesData[CoinIds.ETH]?.price : undefined; - if (!ethPriceUSD || !treeExpectedRewardPerPnk) return "N/A"; - const pnkNeeded = treeExpectedRewardPerPnk * ethPriceUSD; - return beautifyStatNumber(pnkNeeded, true); - }, - getSubtext: (data, coinPrice) => { - const treeExpectedRewardPerPnk = data?.treeExpectedRewardPerPnk; - const ethPriceUSD = pricesData ? pricesData[CoinIds.ETH]?.price : undefined; - if (!ethPriceUSD || !treeExpectedRewardPerPnk) return "N/A"; - const pnkNeeded = treeExpectedRewardPerPnk * ethPriceUSD; - return formatUSD(unbeautifyStatNumber(beautifyStatNumber(pnkNeeded, true)) * (coinPrice ?? 0)); - }, - color: "purple", - icon: PNKUSDIcon, - }, - { - title: ( - - PNK for 1 ETH - - ), - coinId: 0, - getText: (data) => { - const treeExpectedRewardPerPnk = data?.treeExpectedRewardPerPnk; - if (!treeExpectedRewardPerPnk) return "N/A"; - const pnkNeeded = treeExpectedRewardPerPnk; - return beautifyStatNumber(pnkNeeded, true); - }, - getSubtext: (data, coinPrice) => { - const treeExpectedRewardPerPnk = data?.treeExpectedRewardPerPnk; - if (!treeExpectedRewardPerPnk) return "N/A"; - const pnkNeeded = treeExpectedRewardPerPnk; - return formatUSD(unbeautifyStatNumber(beautifyStatNumber(pnkNeeded, true)) * (coinPrice ?? 0)); - }, - color: "blue", - icon: PNKETHIcon, - }, - ]; - return ( { { title: "Statistics", body: ( - <> - - - All time - - - {stats.map(({ title, coinId, getText, getSubtext, color, icon }) => { - const coinPrice = !isUndefined(pricesData) ? pricesData[coinIds[coinId!]]?.price : undefined; - return ( - } - subtext={calculateSubtextRender(data?.court, getSubtext, coinPrice)} - /> - ); - })} - - - - ({ - value: range.value, - text: range.text, - }))} - defaultValue={selectedRange} - callback={handleTimeRangeChange} - /> - - - - {timeframedStats.map(({ title, coinId, getText, getSubtext, color, icon }) => { - const coinPrice = !isUndefined(pricesData) ? pricesData[coinIds[coinId!]]?.price : undefined; - return ( - } - subtext={calculateSubtextRender(foundCourt, getSubtext, coinPrice)} - /> - ); - })} - - + +
+ + + 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} + /> + ); + })} + +
+
), }, ]} diff --git a/web/src/pages/Courts/CourtDetails/index.tsx b/web/src/pages/Courts/CourtDetails/index.tsx index 2d1ee9777..540cdc663 100644 --- a/web/src/pages/Courts/CourtDetails/index.tsx +++ b/web/src/pages/Courts/CourtDetails/index.tsx @@ -20,6 +20,7 @@ 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"; @@ -33,13 +34,7 @@ const CourtHeader = styled.h1` justify-content: space-between; gap: 24px; flex-wrap: wrap; - margin-bottom: 24px; - - ${landscapeStyle( - () => css` - margin-bottom: 32px; - ` - )}; + margin-bottom: 16px; `; const CourtInfo = styled.div` @@ -64,7 +59,6 @@ const ButtonContainer = styled.div` const StyledCard = styled(Card)` padding: ${responsiveSize(16, 32)}; - padding-bottom: 16px; margin-top: 12px; width: 100%; height: auto; @@ -78,6 +72,24 @@ const StyledBreadcrumb = styled(Breadcrumb)` } `; +const StakePanelAndStats = styled.div` + display: flex; + flex-direction: row; + justify-content: space-between; + margin-top: 24px; + gap: 20px; + flex-wrap: wrap; + + ${landscapeStyle( + () => css` + & > * { + flex: 1 1 calc(50% - 10px); + max-width: calc(50% - 10px); + } + ` + )} +`; + const CourtDetails: React.FC = () => { const { id } = useParams(); const { data: policy } = useCourtPolicy(id); @@ -111,8 +123,11 @@ const CourtDetails: React.FC = () => { /> - - + + + + + diff --git a/web/src/pages/Home/CourtOverview/ExtraStats.tsx b/web/src/pages/Home/CourtOverview/ExtraStats.tsx index c75517ed7..8d1f6253b 100644 --- a/web/src/pages/Home/CourtOverview/ExtraStats.tsx +++ b/web/src/pages/Home/CourtOverview/ExtraStats.tsx @@ -17,6 +17,12 @@ const StyledCard = styled.div` justify-content: center; `; +const StyledLabel = styled.label` + margin-top: 24px; + font-size: 14px; + font-weight: 600; +`; + interface IStat { title: string; getText: (data) => string; @@ -44,15 +50,12 @@ const stats: IStat[] = [ const timeRanges = [ { value: 7, text: "Last 7 days" }, { value: 30, text: "Last 30 days" }, - { value: 90, text: "Last 90 days" }, - // we can uncomment these as the contract deployment time increases - // { value: 180, text: "Last 180 days" }, - // { value: 365, text: "Last 365 days" }, + { value: 180, text: "Last 180 days" }, { value: "allTime", text: "All Time" }, ]; const ExtraStats = () => { - const [selectedRange, setSelectedRange] = useState(timeRanges[0].value); + const [selectedRange, setSelectedRange] = useState(timeRanges[1].value); const data = useHomePageExtraStats(selectedRange); const handleTimeRangeChange = (value: string | number) => { @@ -77,9 +80,13 @@ const ExtraStats = () => { } icon={LawBalance} /> - {stats.map(({ title, getText, icon }) => ( - - ))} + {data.data?.mostDisputedCourt?.numberDisputes === 0 ? ( + No activity in this period + ) : ( + stats.map(({ title, getText, icon }) => ( + + )) + )} ); }; diff --git a/web/src/pages/Home/CourtOverview/Stats.tsx b/web/src/pages/Home/CourtOverview/Stats.tsx index d7d7ecedb..b194aaf6d 100644 --- a/web/src/pages/Home/CourtOverview/Stats.tsx +++ b/web/src/pages/Home/CourtOverview/Stats.tsx @@ -1,5 +1,5 @@ import React from "react"; -import styled, { css } from "styled-components"; +import styled from "styled-components"; import { Card } from "@kleros/ui-components-library"; @@ -16,7 +16,6 @@ import { calculateSubtextRender } from "utils/calculateSubtextRender"; import { formatETH, formatPNK, formatUnitsWei, formatUSD } from "utils/format"; import { isUndefined } from "utils/index"; -import { landscapeStyle } from "styles/landscapeStyle"; import { responsiveSize } from "styles/responsiveSize"; import StatDisplay, { IStatDisplay } from "components/StatDisplay"; @@ -25,19 +24,10 @@ import { StyledSkeleton } from "components/StyledSkeleton"; const StyledCard = styled(Card)` width: auto; height: fit-content; - gap: 32px; - padding: ${responsiveSize(16, 30)}; - padding-left: ${responsiveSize(16, 35)}; - padding-bottom: 16px; + gap: 32px 0; + padding: ${responsiveSize(16, 32)}; display: grid; grid-template-columns: repeat(auto-fit, minmax(190px, 1fr)); - - ${landscapeStyle( - () => css` - padding-bottom: 0px; - gap: 0px; - ` - )} `; const getLastOrZero = (src: HomePageQuery["counters"], stat: HomePageQueryDataPoints) => @@ -109,6 +99,7 @@ const Stats = () => { {...{ title, color, icon }} text={data ? getText(data["counters"]) : } subtext={calculateSubtextRender(data ? data["counters"] : undefined, getSubtext, coinPrice)} + isSmallDisplay={false} /> ); })} diff --git a/web/src/utils/userLevelCalculation.ts b/web/src/utils/userLevelCalculation.ts index 734e6917b..a25aa8884 100644 --- a/web/src/utils/userLevelCalculation.ts +++ b/web/src/utils/userLevelCalculation.ts @@ -7,11 +7,11 @@ interface ILevelCriteria { } const levelCriteria: ILevelCriteria[] = [ - { level: 0, title: "Diogenes", minDisputes: 3, minScore: 0, maxScore: 49 }, - { level: 1, title: "Pythagoras", minDisputes: 0, minScore: 0, maxScore: 70 }, - { level: 2, title: "Socrates", minDisputes: 3, minScore: 71, maxScore: 80 }, - { level: 3, title: "Plato", minDisputes: 7, minScore: 81, maxScore: 90 }, { level: 4, title: "Aristotle", minDisputes: 10, minScore: 91, maxScore: 100 }, + { level: 3, title: "Plato", minDisputes: 7, minScore: 81, maxScore: 90 }, + { level: 2, title: "Socrates", minDisputes: 3, minScore: 71, maxScore: 80 }, + { level: 1, title: "Pythagoras", minDisputes: 0, minScore: 0, maxScore: 70 }, + { level: 0, title: "Diogenes", minDisputes: 3, minScore: 0, maxScore: 49 }, ]; export const getUserLevelData = (coherenceScore: number, totalResolvedDisputes: number) => { @@ -21,7 +21,7 @@ export const getUserLevelData = (coherenceScore: number, totalResolvedDisputes: coherenceScore >= criteria.minScore && coherenceScore <= criteria.maxScore ) { - return levelCriteria.find(({ level }) => level === criteria.level); + return criteria; } }