From 45bc1fb8658e48914b28e2c94822f8fd9e629f46 Mon Sep 17 00:00:00 2001 From: kemuru <102478601+kemuru@users.noreply.github.com> Date: Fri, 31 Jan 2025 19:29:54 +0100 Subject: [PATCH 1/7] feat: setup new profile page structure, new juror card style, add tabs, style tweaks, refactors --- .../JurorLink.tsx} | 26 +++++-- .../Popup/MiniGuides/JurorLevels.tsx | 4 +- .../Voting/VotesDetails/AccordionTitle.tsx | 4 +- .../Home/TopJurors/JurorCard/DesktopCard.tsx | 4 +- .../Home/TopJurors/JurorCard/JurorLevel.tsx | 2 +- .../Home/TopJurors/JurorCard/MobileCard.tsx | 4 +- .../{ => BottomContent}/Coherence.tsx | 9 ++- .../{ => BottomContent}/JurorRewards.tsx | 31 ++++++--- .../{ => BottomContent}/PixelArt.tsx | 9 ++- .../Profile/JurorInfo/BottomContent/index.tsx | 69 +++++++++++++++++++ web/src/pages/Profile/JurorInfo/Header.tsx | 25 +------ .../Profile/JurorInfo/StakingRewards.tsx | 29 +++++--- .../Profile/JurorInfo/TopContent/index.tsx | 35 ++++++++++ web/src/pages/Profile/JurorInfo/index.tsx | 25 +++---- .../CourtCard/CourtName.tsx | 0 .../{Courts => Stakes}/CourtCard/Stake.tsx | 0 .../{Courts => Stakes}/CourtCard/index.tsx | 0 .../Profile/{Courts => Stakes}/Header.tsx | 5 +- .../Profile/{Courts => Stakes}/index.tsx | 15 ++-- web/src/pages/Profile/index.tsx | 50 +++++++++----- web/src/utils/userLevelCalculation.ts | 2 +- 21 files changed, 235 insertions(+), 113 deletions(-) rename web/src/{pages/Home/TopJurors/JurorCard/JurorTitle.tsx => components/JurorLink.tsx} (61%) rename web/src/pages/Profile/JurorInfo/{ => BottomContent}/Coherence.tsx (94%) rename web/src/pages/Profile/JurorInfo/{ => BottomContent}/JurorRewards.tsx (67%) rename web/src/pages/Profile/JurorInfo/{ => BottomContent}/PixelArt.tsx (93%) create mode 100644 web/src/pages/Profile/JurorInfo/BottomContent/index.tsx create mode 100644 web/src/pages/Profile/JurorInfo/TopContent/index.tsx rename web/src/pages/Profile/{Courts => Stakes}/CourtCard/CourtName.tsx (100%) rename web/src/pages/Profile/{Courts => Stakes}/CourtCard/Stake.tsx (100%) rename web/src/pages/Profile/{Courts => Stakes}/CourtCard/index.tsx (100%) rename web/src/pages/Profile/{Courts => Stakes}/Header.tsx (86%) rename web/src/pages/Profile/{Courts => Stakes}/index.tsx (78%) diff --git a/web/src/pages/Home/TopJurors/JurorCard/JurorTitle.tsx b/web/src/components/JurorLink.tsx similarity index 61% rename from web/src/pages/Home/TopJurors/JurorCard/JurorTitle.tsx rename to web/src/components/JurorLink.tsx index 437aea26a..212c5605b 100644 --- a/web/src/pages/Home/TopJurors/JurorCard/JurorTitle.tsx +++ b/web/src/components/JurorLink.tsx @@ -1,11 +1,15 @@ -import React from "react"; +import React, { useMemo } from "react"; import styled from "styled-components"; +import { useAccount } from "wagmi"; + +import { DEFAULT_CHAIN, getChain } from "consts/chains"; + import ArrowIcon from "svgs/icons/arrow.svg"; +import NewTabIcon from "svgs/icons/new-tab.svg"; import { IdenticonOrAvatar, AddressOrName } from "components/ConnectWallet/AccountDisplay"; import { StyledArrowLink } from "components/StyledArrowLink"; -import { useAccount } from "wagmi"; const Container = styled.div` display: flex; @@ -36,26 +40,34 @@ export const ReStyledArrowLink = styled(StyledArrowLink)` } `; -interface IJurorTitle { +interface IJurorLink { address: string; + isInternalLink?: boolean; } -const JurorTitle: React.FC = ({ address }) => { +const JurorLink: React.FC = ({ address, isInternalLink = true }) => { const { isConnected, address: connectedAddress } = useAccount(); const profileLink = isConnected && connectedAddress?.toLowerCase() === address.toLowerCase() ? "/profile/1/desc/all" : `/profile/1/desc/all?address=${address}`; + const addressExplorerLink = useMemo(() => { + return `${getChain(DEFAULT_CHAIN)?.blockExplorers?.default.url}/address/${address}`; + }, [address]); return ( - + - + {isInternalLink ? : } ); }; -export default JurorTitle; +export default JurorLink; diff --git a/web/src/components/Popup/MiniGuides/JurorLevels.tsx b/web/src/components/Popup/MiniGuides/JurorLevels.tsx index b8043e26f..8dc0b0ede 100644 --- a/web/src/components/Popup/MiniGuides/JurorLevels.tsx +++ b/web/src/components/Popup/MiniGuides/JurorLevels.tsx @@ -5,8 +5,8 @@ import { Card as _Card } from "@kleros/ui-components-library"; import { landscapeStyle } from "styles/landscapeStyle"; -import Coherence from "pages/Profile/JurorInfo/Coherence"; -import PixelArt from "pages/Profile/JurorInfo/PixelArt"; +import Coherence from "pages/Profile/JurorInfo/BottomContent/Coherence"; +import PixelArt from "pages/Profile/JurorInfo/BottomContent/PixelArt"; import Template from "./MainStructureTemplate"; import { Title, ParagraphsContainer, LeftContentContainer } from "./PageContentsTemplate"; diff --git a/web/src/pages/Cases/CaseDetails/Voting/VotesDetails/AccordionTitle.tsx b/web/src/pages/Cases/CaseDetails/Voting/VotesDetails/AccordionTitle.tsx index 6c208351c..bc48524c8 100644 --- a/web/src/pages/Cases/CaseDetails/Voting/VotesDetails/AccordionTitle.tsx +++ b/web/src/pages/Cases/CaseDetails/Voting/VotesDetails/AccordionTitle.tsx @@ -8,7 +8,7 @@ import { getVoteChoice } from "utils/getVoteChoice"; import { isUndefined } from "utils/index"; import { InternalLink } from "components/InternalLink"; -import JurorTitle from "pages/Home/TopJurors/JurorCard/JurorTitle"; +import JurorLink from "components/JurorLink"; const TitleContainer = styled.div` display: flex; @@ -92,7 +92,7 @@ const AccordionTitle: React.FC<{ - + diff --git a/web/src/pages/Home/TopJurors/JurorCard/DesktopCard.tsx b/web/src/pages/Home/TopJurors/JurorCard/DesktopCard.tsx index 1538dcd67..e6a843201 100644 --- a/web/src/pages/Home/TopJurors/JurorCard/DesktopCard.tsx +++ b/web/src/pages/Home/TopJurors/JurorCard/DesktopCard.tsx @@ -7,9 +7,9 @@ import { hoverShortTransitionTiming } from "styles/commonStyles"; import Coherence from "./Coherence"; import JurorLevel from "./JurorLevel"; -import JurorTitle from "./JurorTitle"; import Rank from "./Rank"; import Rewards from "./Rewards"; +import JurorLink from "components/JurorLink"; const Container = styled.div<{ renderRank?: boolean }>` ${hoverShortTransitionTiming} @@ -57,7 +57,7 @@ const DesktopCard: React.FC = ({ return ( {renderRank && } - + diff --git a/web/src/pages/Home/TopJurors/JurorCard/JurorLevel.tsx b/web/src/pages/Home/TopJurors/JurorCard/JurorLevel.tsx index eba34f350..3a0b114fb 100644 --- a/web/src/pages/Home/TopJurors/JurorCard/JurorLevel.tsx +++ b/web/src/pages/Home/TopJurors/JurorCard/JurorLevel.tsx @@ -6,7 +6,7 @@ import { landscapeStyle } from "styles/landscapeStyle"; import { getUserLevelData } from "utils/userLevelCalculation"; import { getCoherencePercent } from "utils/getCoherencePercent"; -import PixelArt from "pages/Profile/JurorInfo/PixelArt"; +import PixelArt from "pages/Profile/JurorInfo/BottomContent/PixelArt"; const Container = styled.div` display: flex; diff --git a/web/src/pages/Home/TopJurors/JurorCard/MobileCard.tsx b/web/src/pages/Home/TopJurors/JurorCard/MobileCard.tsx index faffb821a..5118670a6 100644 --- a/web/src/pages/Home/TopJurors/JurorCard/MobileCard.tsx +++ b/web/src/pages/Home/TopJurors/JurorCard/MobileCard.tsx @@ -9,9 +9,9 @@ import HeaderRewards from "../Header/Rewards"; import Coherence from "./Coherence"; import JurorLevel from "./JurorLevel"; -import JurorTitle from "./JurorTitle"; import Rank from "./Rank"; import Rewards from "./Rewards"; +import JurorLink from "components/JurorLink"; const Container = styled.div` ${hoverShortTransitionTiming} @@ -97,7 +97,7 @@ const MobileCard: React.FC = ({ {rank ? : null} - + diff --git a/web/src/pages/Profile/JurorInfo/Coherence.tsx b/web/src/pages/Profile/JurorInfo/BottomContent/Coherence.tsx similarity index 94% rename from web/src/pages/Profile/JurorInfo/Coherence.tsx rename to web/src/pages/Profile/JurorInfo/BottomContent/Coherence.tsx index 712884d22..44e1f9786 100644 --- a/web/src/pages/Profile/JurorInfo/Coherence.tsx +++ b/web/src/pages/Profile/JurorInfo/BottomContent/Coherence.tsx @@ -1,9 +1,11 @@ import React from "react"; import styled, { css } from "styled-components"; +import { landscapeStyle } from "styles/landscapeStyle"; + import { CircularProgress } from "@kleros/ui-components-library"; -import { landscapeStyle } from "styles/landscapeStyle"; +import { ILevelCriteria } from "utils/userLevelCalculation"; import WithHelpTooltip from "components/WithHelpTooltip"; @@ -26,10 +28,7 @@ const tooltipMsg = " the majority of jurors it's considered a Coherent Vote."; interface ICoherence { - userLevelData: { - level: number; - title: string; - }; + userLevelData: ILevelCriteria; totalCoherentVotes: number; totalResolvedVotes: number; isMiniGuide: boolean; diff --git a/web/src/pages/Profile/JurorInfo/JurorRewards.tsx b/web/src/pages/Profile/JurorInfo/BottomContent/JurorRewards.tsx similarity index 67% rename from web/src/pages/Profile/JurorInfo/JurorRewards.tsx rename to web/src/pages/Profile/JurorInfo/BottomContent/JurorRewards.tsx index 0a4471252..96ee2f4a7 100644 --- a/web/src/pages/Profile/JurorInfo/JurorRewards.tsx +++ b/web/src/pages/Profile/JurorInfo/BottomContent/JurorRewards.tsx @@ -1,7 +1,7 @@ import React from "react"; -import styled from "styled-components"; +import styled, { css } from "styled-components"; -import { useAccount } from "wagmi"; +import { landscapeStyle } from "styles/landscapeStyle"; import { CoinIds } from "consts/coingecko"; import { useCoinPrice } from "hooks/useCoinPrice"; @@ -10,14 +10,27 @@ import { getFormattedRewards } from "utils/jurorRewardConfig"; import { useUserQuery } from "queries/useUser"; import WithHelpTooltip from "components/WithHelpTooltip"; - -import TokenRewards from "./TokenRewards"; +import TokenRewards from "../TokenRewards"; const Container = styled.div` display: flex; flex-direction: column; - align-items: flex-start; + align-items: center; width: auto; + gap: 24px; + + ${landscapeStyle( + () => css` + align-items: flex-start; + ` + )} +`; + +const TokenRewardsContainer = styled.div` + display: flex; + flex-direction: column; + align-items: start; + gap: 16px; `; const tooltipMsg = @@ -42,9 +55,11 @@ const JurorRewards: React.FC = ({ addressToQuery }) => { - {formattedRewards.map(({ token, amount, value }) => ( - - ))} + + {formattedRewards.map(({ token, amount, value }) => ( + + ))} + ); }; diff --git a/web/src/pages/Profile/JurorInfo/PixelArt.tsx b/web/src/pages/Profile/JurorInfo/BottomContent/PixelArt.tsx similarity index 93% rename from web/src/pages/Profile/JurorInfo/PixelArt.tsx rename to web/src/pages/Profile/JurorInfo/BottomContent/PixelArt.tsx index 48e15dd93..59a909a82 100644 --- a/web/src/pages/Profile/JurorInfo/PixelArt.tsx +++ b/web/src/pages/Profile/JurorInfo/BottomContent/PixelArt.tsx @@ -9,6 +9,11 @@ import platoImage from "assets/pngs/dashboard/plato.png"; import pythagorasImage from "assets/pngs/dashboard/pythagoras.png"; import socratesImage from "assets/pngs/dashboard/socrates.png"; +const Container = styled.div` + display: flex; + justify-content: center; +`; + interface IStyledImage { show: boolean; width: number | string; @@ -42,7 +47,7 @@ interface IPixelArt { const PixelArt: React.FC = ({ level, width, height }) => { const [imageLoaded, setImageLoaded] = useState(false); return ( -
+ {!imageLoaded && } = ({ level, width, height }) => { width={width} height={height} /> -
+ ); }; diff --git a/web/src/pages/Profile/JurorInfo/BottomContent/index.tsx b/web/src/pages/Profile/JurorInfo/BottomContent/index.tsx new file mode 100644 index 000000000..4d49c13e9 --- /dev/null +++ b/web/src/pages/Profile/JurorInfo/BottomContent/index.tsx @@ -0,0 +1,69 @@ +import React from "react"; +import styled, { css } from "styled-components"; + +import { landscapeStyle } from "styles/landscapeStyle"; + +import { ILevelCriteria } from "utils/userLevelCalculation"; + +import PixelArt from "./PixelArt"; +import Coherence from "./Coherence"; +import JurorRewards from "./JurorRewards"; +import StakingRewards from "../StakingRewards"; + +const Container = styled.div` + display: flex; + flex-direction: column; + flex-wrap: wrap; + justify-content: space-between; + align-items: center; + + gap: 32px; + width: 100%; + height: auto; + + ${landscapeStyle( + () => css` + flex-direction: row; + align-items: flex-start; + ` + )} +`; + +const LeftContent = styled.div` + display: flex; + flex-direction: row; + gap: 48px; + flex-direction: column; + + ${landscapeStyle( + () => css` + flex-direction: row; + ` + )} +`; + +interface IBottomContent { + userLevelData: ILevelCriteria; + totalCoherentVotes: number; + totalResolvedVotes: number; + addressToQuery: `0x${string}`; +} + +const BottomContent: React.FC = ({ + userLevelData, + totalCoherentVotes, + totalResolvedVotes, + addressToQuery, +}) => { + return ( + + + + + + + + + ); +}; +export default BottomContent; diff --git a/web/src/pages/Profile/JurorInfo/Header.tsx b/web/src/pages/Profile/JurorInfo/Header.tsx index 8e90df5b6..13c4e88be 100644 --- a/web/src/pages/Profile/JurorInfo/Header.tsx +++ b/web/src/pages/Profile/JurorInfo/Header.tsx @@ -1,17 +1,13 @@ -import React, { useMemo } from "react"; +import React from "react"; import styled from "styled-components"; import { responsiveSize } from "styles/responsiveSize"; import { useToggle } from "react-use"; import { useSearchParams } from "react-router-dom"; -import { Copiable } from "@kleros/ui-components-library"; import XIcon from "svgs/socialmedia/x.svg"; -import { DEFAULT_CHAIN, getChain } from "consts/chains"; -import { shortenAddress } from "utils/shortenAddress"; - import HowItWorks from "components/HowItWorks"; import JurorLevels from "components/Popup/MiniGuides/JurorLevels"; import { ExternalLink } from "components/ExternalLink"; @@ -51,12 +47,6 @@ const StyledLink = styled(ExternalLink)` gap: 8px; `; -const StyledExternalLink = styled(ExternalLink)` - font-size: ${responsiveSize(18, 22)}; - margin-left: ${responsiveSize(4, 8)}; - font-weight: 600; -`; - interface IHeader { levelTitle: string; levelNumber: number; @@ -81,20 +71,9 @@ const Header: React.FC = ({ const xShareUrl = `https://twitter.com/intent/tweet?text=${encodeURIComponent(xPostText)}`; const searchParamAddress = searchParams.get("address")?.toLowerCase(); - const addressExplorerLink = useMemo(() => { - return `${getChain(DEFAULT_CHAIN)?.blockExplorers?.default.url}/address/${addressToQuery}`; - }, [addressToQuery]); - return ( - - Juror Profile - - - - {shortenAddress(addressToQuery)} - - - + Juror Profile { const tooltipMsg = "Staking Rewards are the rewards won by staking your PNK on a court during " + - "the Kleros' Jurors incentive program."; + "the Kleros' Jurors incentive program. This will start as soon as the " + + "corresponding KIP (Kleros Improvement Proposal) goes into effect."; -const Coherence: React.FC = () => { +const StakingRewards: React.FC = () => { return ( + // + // + // + // Coming soon + // + // + // + // - + - - + ); }; -export default Coherence; +export default StakingRewards; diff --git a/web/src/pages/Profile/JurorInfo/TopContent/index.tsx b/web/src/pages/Profile/JurorInfo/TopContent/index.tsx new file mode 100644 index 000000000..d917ff537 --- /dev/null +++ b/web/src/pages/Profile/JurorInfo/TopContent/index.tsx @@ -0,0 +1,35 @@ +import React from "react"; +import styled from "styled-components"; + +import JurorLink from "components/JurorLink"; + +const Container = styled.div` + display: flex; + flex-direction: row + align-items: center; + gap: 16px 24px; + flex-wrap: wrap; +`; + +const StyledLabel = styled.label` + font-size: 14px; +`; + +interface ITopContent { + address: `0x${string}`; + totalResolvedDisputes: number; +} + +const TopContent: React.FC = ({ address, totalResolvedDisputes }) => { + return ( + + + {totalResolvedDisputes > 0 ? ( + + Juror in {totalResolvedDisputes} {totalResolvedDisputes === 1 ? "case" : "cases"} + + ) : null} + + ); +}; +export default TopContent; diff --git a/web/src/pages/Profile/JurorInfo/index.tsx b/web/src/pages/Profile/JurorInfo/index.tsx index f85122738..193d61d8c 100644 --- a/web/src/pages/Profile/JurorInfo/index.tsx +++ b/web/src/pages/Profile/JurorInfo/index.tsx @@ -11,31 +11,22 @@ import { useUserQuery } from "queries/useUser"; import { landscapeStyle } from "styles/landscapeStyle"; import { responsiveSize } from "styles/responsiveSize"; -import Coherence from "./Coherence"; import Header from "./Header"; -import JurorRewards from "./JurorRewards"; -import PixelArt from "./PixelArt"; +import BottomContent from "./BottomContent"; +import { Divider } from "components/Divider"; +import TopContent from "./TopContent"; const Container = styled.div``; const Card = styled(_Card)` display: flex; flex-direction: column; - align-items: center; justify-content: center; - gap: 40px; + gap: 24px; width: 100%; height: auto; - padding: 24px 0; - - ${landscapeStyle( - () => css` - flex-direction: row; - gap: ${responsiveSize(24, 64)}; - height: 236px; - ` - )} + padding: 24px; `; interface IJurorInfo { @@ -58,9 +49,9 @@ const JurorInfo: React.FC = ({ addressToQuery }) => { {...{ totalCoherentVotes, totalResolvedVotes, addressToQuery }} /> - - - + + + ); diff --git a/web/src/pages/Profile/Courts/CourtCard/CourtName.tsx b/web/src/pages/Profile/Stakes/CourtCard/CourtName.tsx similarity index 100% rename from web/src/pages/Profile/Courts/CourtCard/CourtName.tsx rename to web/src/pages/Profile/Stakes/CourtCard/CourtName.tsx diff --git a/web/src/pages/Profile/Courts/CourtCard/Stake.tsx b/web/src/pages/Profile/Stakes/CourtCard/Stake.tsx similarity index 100% rename from web/src/pages/Profile/Courts/CourtCard/Stake.tsx rename to web/src/pages/Profile/Stakes/CourtCard/Stake.tsx diff --git a/web/src/pages/Profile/Courts/CourtCard/index.tsx b/web/src/pages/Profile/Stakes/CourtCard/index.tsx similarity index 100% rename from web/src/pages/Profile/Courts/CourtCard/index.tsx rename to web/src/pages/Profile/Stakes/CourtCard/index.tsx diff --git a/web/src/pages/Profile/Courts/Header.tsx b/web/src/pages/Profile/Stakes/Header.tsx similarity index 86% rename from web/src/pages/Profile/Courts/Header.tsx rename to web/src/pages/Profile/Stakes/Header.tsx index 07f61e11c..93c7f3dd9 100644 --- a/web/src/pages/Profile/Courts/Header.tsx +++ b/web/src/pages/Profile/Stakes/Header.tsx @@ -2,7 +2,6 @@ import React from "react"; import styled, { css } from "styled-components"; import { formatUnits } from "viem"; -import { useSearchParams } from "react-router-dom"; import LockerIcon from "svgs/icons/locker.svg"; @@ -58,12 +57,10 @@ interface IHeader { const Header: React.FC = ({ lockedStake }) => { const formattedLockedStake = !isUndefined(lockedStake) && formatUnits(lockedStake, 18); - const [searchParams] = useSearchParams(); - const searchParamAddress = searchParams.get("address")?.toLowerCase(); return ( - {searchParamAddress ? "Their" : "My"} Courts + Stakes {!isUndefined(lockedStake) ? ( diff --git a/web/src/pages/Profile/Courts/index.tsx b/web/src/pages/Profile/Stakes/index.tsx similarity index 78% rename from web/src/pages/Profile/Courts/index.tsx rename to web/src/pages/Profile/Stakes/index.tsx index 512478b1d..858d1cabf 100644 --- a/web/src/pages/Profile/Courts/index.tsx +++ b/web/src/pages/Profile/Stakes/index.tsx @@ -2,7 +2,6 @@ import React from "react"; import styled, { css } from "styled-components"; import Skeleton from "react-loading-skeleton"; -import { useSearchParams } from "react-router-dom"; import { useReadSortitionModuleGetJurorBalance } from "hooks/contracts/generated"; @@ -15,7 +14,7 @@ import CourtCard from "./CourtCard"; import Header from "./Header"; const Container = styled.div` - margin-top: ${responsiveSize(24, 48)}; + margin-top: ${responsiveSize(24, 32)}; `; const CourtCardsContainer = styled.div` @@ -35,17 +34,15 @@ const StyledLabel = styled.label` font-size: ${responsiveSize(14, 16)}; `; -interface ICourts { +interface IStakes { addressToQuery: `0x${string}`; } -const Courts: React.FC = ({ addressToQuery }) => { +const Stakes: React.FC = ({ addressToQuery }) => { const { data: stakeData, isLoading } = useJurorStakeDetailsQuery(addressToQuery); const { data: jurorBalance } = useReadSortitionModuleGetJurorBalance({ args: [addressToQuery, BigInt(1)], }); - const [searchParams] = useSearchParams(); - const searchParamAddress = searchParams.get("address")?.toLowerCase(); const stakedCourts = stakeData?.jurorTokensPerCourts?.filter(({ staked }) => staked > 0); const isStaked = stakedCourts && stakedCourts.length > 0; const lockedStake = jurorBalance?.[1]; @@ -54,9 +51,7 @@ const Courts: React.FC = ({ addressToQuery }) => {
{isLoading ? : null} - {!isStaked && !isLoading ? ( - {searchParamAddress ? "They" : "You"} are not staked in any court - ) : null} + {!isStaked && !isLoading ? No stakes found : null} {isStaked && !isLoading ? ( {stakeData?.jurorTokensPerCourts @@ -70,4 +65,4 @@ const Courts: React.FC = ({ addressToQuery }) => { ); }; -export default Courts; +export default Stakes; diff --git a/web/src/pages/Profile/index.tsx b/web/src/pages/Profile/index.tsx index 8a329a719..4a554684c 100644 --- a/web/src/pages/Profile/index.tsx +++ b/web/src/pages/Profile/index.tsx @@ -1,4 +1,4 @@ -import React, { useMemo } from "react"; +import React, { useState, useMemo } from "react"; import styled, { css } from "styled-components"; import { MAX_WIDTH_LANDSCAPE, landscapeStyle } from "styles/landscapeStyle"; @@ -6,19 +6,19 @@ import { responsiveSize } from "styles/responsiveSize"; import { useNavigate, useParams, useSearchParams } from "react-router-dom"; import { useAccount } from "wagmi"; +import { Tabs as TabsComponent } from "@kleros/ui-components-library"; import { isUndefined } from "utils/index"; import { decodeURIFilter, useRootPath } from "utils/uri"; import { DisputeDetailsFragment, useMyCasesQuery } from "queries/useCasesQuery"; import { useUserQuery } from "queries/useUser"; import { OrderDirection } from "src/graphql/graphql"; - import CasesDisplay from "components/CasesDisplay"; import ConnectWallet from "components/ConnectWallet"; import FavoriteCases from "components/FavoriteCases"; import ScrollTop from "components/ScrollTop"; -import Courts from "./Courts"; import JurorInfo from "./JurorInfo"; +import Stakes from "./Stakes"; const Container = styled.div` width: 100%; @@ -35,13 +35,18 @@ const Container = styled.div` `; const StyledCasesDisplay = styled(CasesDisplay)` - margin-top: ${responsiveSize(24, 48)}; + margin-top: ${responsiveSize(24, 32)}; .title { margin-bottom: ${responsiveSize(12, 24)}; } `; +const StyledTabs = styled(TabsComponent)` + width: 100%; + margin-top: ${responsiveSize(16, 32)}; +`; + const ConnectWalletContainer = styled.div` display: flex; flex-direction: column; @@ -50,6 +55,12 @@ const ConnectWalletContainer = styled.div` color: ${({ theme }) => theme.primaryText}; `; +const TABS = [ + { text: "Stakes", value: 0 }, + { text: "Cases", value: 1 }, + { text: "Votes", value: 2 }, +]; + const Profile: React.FC = () => { const { isConnected, address: connectedAddress } = useAccount(); const { page, order, filter } = useParams(); @@ -75,25 +86,30 @@ const Profile: React.FC = () => { () => (!isUndefined(totalCases) ? Math.ceil(totalCases / casesPerPage) : 1), [totalCases, casesPerPage] ); + const [currentTab, setCurrentTab] = useState(0); return ( {isConnected || searchParamAddress ? ( <> - - - navigate(`${location}/${newPage}/${order}/${filter}?${searchParams.toString()}`) - } - {...{ casesPerPage }} - /> + setCurrentTab(n)} /> + {currentTab === 0 && } + {currentTab === 1 && ( + + navigate(`${location}/${newPage}/${order}/${filter}?${searchParams.toString()}`) + } + {...{ casesPerPage }} + /> + )} + {currentTab === 2 && null} ) : ( diff --git a/web/src/utils/userLevelCalculation.ts b/web/src/utils/userLevelCalculation.ts index 52d504256..60dc56b02 100644 --- a/web/src/utils/userLevelCalculation.ts +++ b/web/src/utils/userLevelCalculation.ts @@ -1,4 +1,4 @@ -interface ILevelCriteria { +export interface ILevelCriteria { level: number; title: string; minDisputes: number; From c3213ca635f27f9f2fbc270937a6990bd26f58c6 Mon Sep 17 00:00:00 2001 From: kemuru <102478601+kemuru@users.noreply.github.com> Date: Sat, 1 Feb 2025 02:44:17 +0100 Subject: [PATCH 2/7] feat: url path tab logic, refactors, add voted ballot svg, add icons to tabs --- web/src/app.tsx | 2 +- web/src/assets/svgs/icons/voted-ballot.svg | 5 + web/src/components/EvidenceCard.tsx | 2 +- web/src/components/JurorLink.tsx | 4 +- .../Popup/MiniGuides/JurorLevels.tsx | 4 +- .../Settings/General/WalletAndProfile.tsx | 2 +- .../Voting/VotesDetails/AccordionTitle.tsx | 2 +- .../Home/TopJurors/JurorCard/JurorLevel.tsx | 2 +- web/src/pages/Jurors/index.tsx | 2 +- web/src/pages/Profile/Cases/index.tsx | 68 +++++++++++ .../BottomContent/Coherence.tsx | 0 .../BottomContent/JurorRewards.tsx | 3 +- .../BottomContent/PixelArt.tsx | 0 .../BottomContent/index.tsx | 0 .../{JurorInfo => JurorCard}/Header.tsx | 0 .../StakingRewards.tsx | 0 .../{JurorInfo => JurorCard}/TokenRewards.tsx | 0 .../TopContent/index.tsx | 0 .../{JurorInfo => JurorCard}/index.tsx | 11 +- web/src/pages/Profile/Stakes/index.tsx | 2 +- web/src/pages/Profile/Votes/index.tsx | 8 ++ web/src/pages/Profile/index.tsx | 107 ++++++++---------- 22 files changed, 146 insertions(+), 78 deletions(-) create mode 100644 web/src/assets/svgs/icons/voted-ballot.svg create mode 100644 web/src/pages/Profile/Cases/index.tsx rename web/src/pages/Profile/{JurorInfo => JurorCard}/BottomContent/Coherence.tsx (100%) rename web/src/pages/Profile/{JurorInfo => JurorCard}/BottomContent/JurorRewards.tsx (98%) rename web/src/pages/Profile/{JurorInfo => JurorCard}/BottomContent/PixelArt.tsx (100%) rename web/src/pages/Profile/{JurorInfo => JurorCard}/BottomContent/index.tsx (100%) rename web/src/pages/Profile/{JurorInfo => JurorCard}/Header.tsx (100%) rename web/src/pages/Profile/{JurorInfo => JurorCard}/StakingRewards.tsx (100%) rename web/src/pages/Profile/{JurorInfo => JurorCard}/TokenRewards.tsx (100%) rename web/src/pages/Profile/{JurorInfo => JurorCard}/TopContent/index.tsx (100%) rename web/src/pages/Profile/{JurorInfo => JurorCard}/index.tsx (85%) create mode 100644 web/src/pages/Profile/Votes/index.tsx diff --git a/web/src/app.tsx b/web/src/app.tsx index 7fc3b8af8..b3f29e38d 100644 --- a/web/src/app.tsx +++ b/web/src/app.tsx @@ -73,7 +73,7 @@ const App: React.FC = () => { } /> }> diff --git a/web/src/assets/svgs/icons/voted-ballot.svg b/web/src/assets/svgs/icons/voted-ballot.svg new file mode 100644 index 000000000..3b9bb7c04 --- /dev/null +++ b/web/src/assets/svgs/icons/voted-ballot.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/web/src/components/EvidenceCard.tsx b/web/src/components/EvidenceCard.tsx index 07c4bb955..43e0e1d4b 100644 --- a/web/src/components/EvidenceCard.tsx +++ b/web/src/components/EvidenceCard.tsx @@ -223,7 +223,7 @@ const EvidenceCard: React.FC = ({ description, fileURI, }) => { - const profileLink = `/profile/1/desc/all?address=${sender}`; + const profileLink = `/profile/stakes?address=${sender}`; const transactionExplorerLink = useMemo(() => { return getTxnExplorerLink(transactionHash ?? ""); diff --git a/web/src/components/JurorLink.tsx b/web/src/components/JurorLink.tsx index 212c5605b..cbb5e192a 100644 --- a/web/src/components/JurorLink.tsx +++ b/web/src/components/JurorLink.tsx @@ -49,8 +49,8 @@ const JurorLink: React.FC = ({ address, isInternalLink = true }) => const { isConnected, address: connectedAddress } = useAccount(); const profileLink = isConnected && connectedAddress?.toLowerCase() === address.toLowerCase() - ? "/profile/1/desc/all" - : `/profile/1/desc/all?address=${address}`; + ? "/profile" + : `/profile/stakes?address=${address}`; const addressExplorerLink = useMemo(() => { return `${getChain(DEFAULT_CHAIN)?.blockExplorers?.default.url}/address/${address}`; }, [address]); diff --git a/web/src/components/Popup/MiniGuides/JurorLevels.tsx b/web/src/components/Popup/MiniGuides/JurorLevels.tsx index 8dc0b0ede..c28de3cb1 100644 --- a/web/src/components/Popup/MiniGuides/JurorLevels.tsx +++ b/web/src/components/Popup/MiniGuides/JurorLevels.tsx @@ -5,8 +5,8 @@ import { Card as _Card } from "@kleros/ui-components-library"; import { landscapeStyle } from "styles/landscapeStyle"; -import Coherence from "pages/Profile/JurorInfo/BottomContent/Coherence"; -import PixelArt from "pages/Profile/JurorInfo/BottomContent/PixelArt"; +import Coherence from "pages/Profile/JurorCard/BottomContent/Coherence"; +import PixelArt from "pages/Profile/JurorCard/BottomContent/PixelArt"; import Template from "./MainStructureTemplate"; import { Title, ParagraphsContainer, LeftContentContainer } from "./PageContentsTemplate"; diff --git a/web/src/layout/Header/navbar/Menu/Settings/General/WalletAndProfile.tsx b/web/src/layout/Header/navbar/Menu/Settings/General/WalletAndProfile.tsx index 57931035c..16d9fe1f6 100644 --- a/web/src/layout/Header/navbar/Menu/Settings/General/WalletAndProfile.tsx +++ b/web/src/layout/Header/navbar/Menu/Settings/General/WalletAndProfile.tsx @@ -52,7 +52,7 @@ const WalletAndProfile: React.FC = ({ toggleIsSettingsOpen }) => { - + My Profile diff --git a/web/src/pages/Cases/CaseDetails/Voting/VotesDetails/AccordionTitle.tsx b/web/src/pages/Cases/CaseDetails/Voting/VotesDetails/AccordionTitle.tsx index bc48524c8..185e87e33 100644 --- a/web/src/pages/Cases/CaseDetails/Voting/VotesDetails/AccordionTitle.tsx +++ b/web/src/pages/Cases/CaseDetails/Voting/VotesDetails/AccordionTitle.tsx @@ -86,7 +86,7 @@ const AccordionTitle: React.FC<{ commited: boolean; hiddenVotes: boolean; }> = ({ juror, choice, voteCount, period, answers, isActiveRound, commited, hiddenVotes }) => { - const profileLink = `/profile/1/desc/all?address=${juror}`; + const profileLink = `/profile/stakes?address=${juror}`; return ( diff --git a/web/src/pages/Home/TopJurors/JurorCard/JurorLevel.tsx b/web/src/pages/Home/TopJurors/JurorCard/JurorLevel.tsx index 3a0b114fb..4dd0d9b9f 100644 --- a/web/src/pages/Home/TopJurors/JurorCard/JurorLevel.tsx +++ b/web/src/pages/Home/TopJurors/JurorCard/JurorLevel.tsx @@ -6,7 +6,7 @@ import { landscapeStyle } from "styles/landscapeStyle"; import { getUserLevelData } from "utils/userLevelCalculation"; import { getCoherencePercent } from "utils/getCoherencePercent"; -import PixelArt from "pages/Profile/JurorInfo/BottomContent/PixelArt"; +import PixelArt from "pages/Profile/JurorCard/BottomContent/PixelArt"; const Container = styled.div` display: flex; diff --git a/web/src/pages/Jurors/index.tsx b/web/src/pages/Jurors/index.tsx index 2d7666521..ff021af90 100644 --- a/web/src/pages/Jurors/index.tsx +++ b/web/src/pages/Jurors/index.tsx @@ -55,7 +55,7 @@ const Jurors: React.FC = () => {
Jurors Leaderboard {isConnected ? ( - + My Profile ) : null} diff --git a/web/src/pages/Profile/Cases/index.tsx b/web/src/pages/Profile/Cases/index.tsx new file mode 100644 index 000000000..296d47c19 --- /dev/null +++ b/web/src/pages/Profile/Cases/index.tsx @@ -0,0 +1,68 @@ +import React, { useMemo } from "react"; +import { useNavigate, useParams, useSearchParams } from "react-router-dom"; + +import styled from "styled-components"; +import { responsiveSize } from "styles/responsiveSize"; + +import { isUndefined } from "utils/index"; +import { decodeURIFilter, useRootPath } from "utils/uri"; + +import { DisputeDetailsFragment, OrderDirection } from "src/graphql/graphql"; +import { useMyCasesQuery } from "queries/useCasesQuery"; +import { useUserQuery } from "queries/useUser"; +import CasesDisplay from "components/CasesDisplay"; + +const StyledCasesDisplay = styled(CasesDisplay)` + margin-top: ${responsiveSize(24, 32)}; + + .title { + margin-bottom: ${responsiveSize(12, 24)}; + } +`; + +interface ICases { + addressToQuery: `0x${string}`; +} + +const Cases: React.FC = ({ addressToQuery }) => { + const { page, order, filter } = useParams(); + const [searchParams] = useSearchParams(); + const location = useRootPath(); + const navigate = useNavigate(); + + const casesPerPage = 3; + const pageNumber = parseInt(page ?? "1"); + const disputeSkip = casesPerPage * (pageNumber - 1); + const decodedFilter = decodeURIFilter(filter ?? "all"); + const { data: disputesData } = useMyCasesQuery( + addressToQuery, + disputeSkip, + decodedFilter, + order === "asc" ? OrderDirection.Asc : OrderDirection.Desc + ); + + const { data: userData } = useUserQuery(addressToQuery, decodedFilter); + const totalCases = userData?.user?.disputes.length; + const totalResolvedCases = parseInt(userData?.user?.totalResolvedDisputes); + const totalPages = useMemo( + () => (!isUndefined(totalCases) ? Math.ceil(totalCases / casesPerPage) : 1), + [totalCases, casesPerPage] + ); + + return ( + + navigate(`${location}/${newPage}/${order}/${filter}?${searchParams.toString()}`) + } + {...{ casesPerPage }} + /> + ); +}; + +export default Cases; diff --git a/web/src/pages/Profile/JurorInfo/BottomContent/Coherence.tsx b/web/src/pages/Profile/JurorCard/BottomContent/Coherence.tsx similarity index 100% rename from web/src/pages/Profile/JurorInfo/BottomContent/Coherence.tsx rename to web/src/pages/Profile/JurorCard/BottomContent/Coherence.tsx diff --git a/web/src/pages/Profile/JurorInfo/BottomContent/JurorRewards.tsx b/web/src/pages/Profile/JurorCard/BottomContent/JurorRewards.tsx similarity index 98% rename from web/src/pages/Profile/JurorInfo/BottomContent/JurorRewards.tsx rename to web/src/pages/Profile/JurorCard/BottomContent/JurorRewards.tsx index 96ee2f4a7..23261df92 100644 --- a/web/src/pages/Profile/JurorInfo/BottomContent/JurorRewards.tsx +++ b/web/src/pages/Profile/JurorCard/BottomContent/JurorRewards.tsx @@ -17,11 +17,12 @@ const Container = styled.div` flex-direction: column; align-items: center; width: auto; - gap: 24px; + gap: 12px; ${landscapeStyle( () => css` align-items: flex-start; + gap: 24px; ` )} `; diff --git a/web/src/pages/Profile/JurorInfo/BottomContent/PixelArt.tsx b/web/src/pages/Profile/JurorCard/BottomContent/PixelArt.tsx similarity index 100% rename from web/src/pages/Profile/JurorInfo/BottomContent/PixelArt.tsx rename to web/src/pages/Profile/JurorCard/BottomContent/PixelArt.tsx diff --git a/web/src/pages/Profile/JurorInfo/BottomContent/index.tsx b/web/src/pages/Profile/JurorCard/BottomContent/index.tsx similarity index 100% rename from web/src/pages/Profile/JurorInfo/BottomContent/index.tsx rename to web/src/pages/Profile/JurorCard/BottomContent/index.tsx diff --git a/web/src/pages/Profile/JurorInfo/Header.tsx b/web/src/pages/Profile/JurorCard/Header.tsx similarity index 100% rename from web/src/pages/Profile/JurorInfo/Header.tsx rename to web/src/pages/Profile/JurorCard/Header.tsx diff --git a/web/src/pages/Profile/JurorInfo/StakingRewards.tsx b/web/src/pages/Profile/JurorCard/StakingRewards.tsx similarity index 100% rename from web/src/pages/Profile/JurorInfo/StakingRewards.tsx rename to web/src/pages/Profile/JurorCard/StakingRewards.tsx diff --git a/web/src/pages/Profile/JurorInfo/TokenRewards.tsx b/web/src/pages/Profile/JurorCard/TokenRewards.tsx similarity index 100% rename from web/src/pages/Profile/JurorInfo/TokenRewards.tsx rename to web/src/pages/Profile/JurorCard/TokenRewards.tsx diff --git a/web/src/pages/Profile/JurorInfo/TopContent/index.tsx b/web/src/pages/Profile/JurorCard/TopContent/index.tsx similarity index 100% rename from web/src/pages/Profile/JurorInfo/TopContent/index.tsx rename to web/src/pages/Profile/JurorCard/TopContent/index.tsx diff --git a/web/src/pages/Profile/JurorInfo/index.tsx b/web/src/pages/Profile/JurorCard/index.tsx similarity index 85% rename from web/src/pages/Profile/JurorInfo/index.tsx rename to web/src/pages/Profile/JurorCard/index.tsx index 193d61d8c..7ba721bad 100644 --- a/web/src/pages/Profile/JurorInfo/index.tsx +++ b/web/src/pages/Profile/JurorCard/index.tsx @@ -1,5 +1,5 @@ import React from "react"; -import styled, { css } from "styled-components"; +import styled from "styled-components"; import { Card as _Card } from "@kleros/ui-components-library"; @@ -8,9 +8,6 @@ import { getCoherencePercent } from "utils/getCoherencePercent"; import { useUserQuery } from "queries/useUser"; -import { landscapeStyle } from "styles/landscapeStyle"; -import { responsiveSize } from "styles/responsiveSize"; - import Header from "./Header"; import BottomContent from "./BottomContent"; import { Divider } from "components/Divider"; @@ -29,11 +26,11 @@ const Card = styled(_Card)` padding: 24px; `; -interface IJurorInfo { +interface IJurorCard { addressToQuery: `0x${string}`; } -const JurorInfo: React.FC = ({ addressToQuery }) => { +const JurorCard: React.FC = ({ addressToQuery }) => { const { data } = useUserQuery(addressToQuery); const totalCoherentVotes = data?.user ? parseInt(data?.user?.totalCoherentVotes) : 0; const totalResolvedVotes = data?.user ? parseInt(data?.user?.totalResolvedVotes) : 0; @@ -57,4 +54,4 @@ const JurorInfo: React.FC = ({ addressToQuery }) => { ); }; -export default JurorInfo; +export default JurorCard; diff --git a/web/src/pages/Profile/Stakes/index.tsx b/web/src/pages/Profile/Stakes/index.tsx index 858d1cabf..5be1921f7 100644 --- a/web/src/pages/Profile/Stakes/index.tsx +++ b/web/src/pages/Profile/Stakes/index.tsx @@ -25,7 +25,7 @@ const CourtCardsContainer = styled.div` ${landscapeStyle( () => css` - gap: 16px; + gap: 8px; ` )} `; diff --git a/web/src/pages/Profile/Votes/index.tsx b/web/src/pages/Profile/Votes/index.tsx new file mode 100644 index 000000000..a93433eb2 --- /dev/null +++ b/web/src/pages/Profile/Votes/index.tsx @@ -0,0 +1,8 @@ +import React from "react"; + +interface IVotes {} + +const Votes: React.FC = () => { + return
; +}; +export default Votes; diff --git a/web/src/pages/Profile/index.tsx b/web/src/pages/Profile/index.tsx index 4a554684c..8e7fc37b7 100644 --- a/web/src/pages/Profile/index.tsx +++ b/web/src/pages/Profile/index.tsx @@ -1,24 +1,22 @@ -import React, { useState, useMemo } from "react"; - +import React from "react"; +import { Routes, Route, useNavigate, useSearchParams, useLocation, Navigate } from "react-router-dom"; +import { useAccount } from "wagmi"; import styled, { css } from "styled-components"; import { MAX_WIDTH_LANDSCAPE, landscapeStyle } from "styles/landscapeStyle"; import { responsiveSize } from "styles/responsiveSize"; - -import { useNavigate, useParams, useSearchParams } from "react-router-dom"; -import { useAccount } from "wagmi"; import { Tabs as TabsComponent } from "@kleros/ui-components-library"; -import { isUndefined } from "utils/index"; -import { decodeURIFilter, useRootPath } from "utils/uri"; -import { DisputeDetailsFragment, useMyCasesQuery } from "queries/useCasesQuery"; -import { useUserQuery } from "queries/useUser"; -import { OrderDirection } from "src/graphql/graphql"; -import CasesDisplay from "components/CasesDisplay"; +import PnkIcon from "svgs/icons/pnk.svg"; +import DocIcon from "svgs/icons/doc.svg"; +import VotedIcon from "svgs/icons/voted-ballot.svg"; + import ConnectWallet from "components/ConnectWallet"; import FavoriteCases from "components/FavoriteCases"; import ScrollTop from "components/ScrollTop"; -import JurorInfo from "./JurorInfo"; +import JurorCard from "./JurorCard"; import Stakes from "./Stakes"; +import Cases from "./Cases"; +import Votes from "./Votes"; const Container = styled.div` width: 100%; @@ -34,17 +32,17 @@ const Container = styled.div` )} `; -const StyledCasesDisplay = styled(CasesDisplay)` - margin-top: ${responsiveSize(24, 32)}; - - .title { - margin-bottom: ${responsiveSize(12, 24)}; - } -`; - const StyledTabs = styled(TabsComponent)` width: 100%; margin-top: ${responsiveSize(16, 32)}; + > * { + display: flex; + flex-wrap: wrap; + font-size: ${responsiveSize(14, 16)}; + > svg { + margin-right: 8px !important; + } + } `; const ConnectWalletContainer = styled.div` @@ -56,60 +54,51 @@ const ConnectWalletContainer = styled.div` `; const TABS = [ - { text: "Stakes", value: 0 }, - { text: "Cases", value: 1 }, - { text: "Votes", value: 2 }, + { text: "Stakes", value: 0, Icon: PnkIcon, path: "stakes" }, + { text: "Cases", value: 1, Icon: DocIcon, path: "cases/1/desc/all" }, + { text: "Votes", value: 2, Icon: VotedIcon, path: "votes" }, ]; +const getTabIndex = (currentPath: string) => { + return TABS.findIndex((tab) => currentPath.includes(tab.path.split("/")[0])); +}; + const Profile: React.FC = () => { const { isConnected, address: connectedAddress } = useAccount(); - const { page, order, filter } = useParams(); const [searchParams] = useSearchParams(); - const location = useRootPath(); + const { pathname } = useLocation(); const navigate = useNavigate(); const searchParamAddress = searchParams.get("address")?.toLowerCase(); const addressToQuery = searchParamAddress || connectedAddress?.toLowerCase(); - const casesPerPage = 3; - const pageNumber = parseInt(page ?? "1"); - const disputeSkip = casesPerPage * (pageNumber - 1); - const decodedFilter = decodeURIFilter(filter ?? "all"); - const { data: disputesData } = useMyCasesQuery( - addressToQuery, - disputeSkip, - decodedFilter, - order === "asc" ? OrderDirection.Asc : OrderDirection.Desc - ); - const { data: userData } = useUserQuery(addressToQuery, decodedFilter); - const totalCases = userData?.user?.disputes.length; - const totalResolvedCases = parseInt(userData?.user?.totalResolvedDisputes); - const totalPages = useMemo( - () => (!isUndefined(totalCases) ? Math.ceil(totalCases / casesPerPage) : 1), - [totalCases, casesPerPage] - ); - const [currentTab, setCurrentTab] = useState(0); + + const handleTabChange = (tabIndex: number) => { + const selectedTab = TABS[tabIndex]; + const basePath = `/profile/${selectedTab.path}`; + const queryParam = searchParamAddress ? `?address=${searchParamAddress}` : ""; + navigate(`${basePath}${queryParam}`); + }; return ( {isConnected || searchParamAddress ? ( <> - - setCurrentTab(n)} /> - {currentTab === 0 && } - {currentTab === 1 && ( - - navigate(`${location}/${newPage}/${order}/${filter}?${searchParams.toString()}`) + + handleTabChange(tabIndex)} + /> + + } /> + } /> + } /> + } - {...{ casesPerPage }} /> - )} - {currentTab === 2 && null} + ) : ( From f701d8fda9d796d2c0199c2b17691122f5ade91c Mon Sep 17 00:00:00 2001 From: kemuru <102478601+kemuru@users.noreply.github.com> Date: Sat, 1 Feb 2025 03:17:24 +0100 Subject: [PATCH 3/7] feat: add support for sharing your own profile, variable renaming --- web/src/pages/Profile/Cases/index.tsx | 8 +++---- .../JurorCard/BottomContent/JurorRewards.tsx | 6 +++--- .../Profile/JurorCard/BottomContent/index.tsx | 6 +++--- web/src/pages/Profile/JurorCard/Header.tsx | 8 ++----- web/src/pages/Profile/JurorCard/index.tsx | 12 +++++------ web/src/pages/Profile/Stakes/index.tsx | 8 +++---- web/src/pages/Profile/index.tsx | 21 ++++++++++++------- 7 files changed, 35 insertions(+), 34 deletions(-) diff --git a/web/src/pages/Profile/Cases/index.tsx b/web/src/pages/Profile/Cases/index.tsx index 296d47c19..52e40d495 100644 --- a/web/src/pages/Profile/Cases/index.tsx +++ b/web/src/pages/Profile/Cases/index.tsx @@ -21,10 +21,10 @@ const StyledCasesDisplay = styled(CasesDisplay)` `; interface ICases { - addressToQuery: `0x${string}`; + searchParamAddress: `0x${string}`; } -const Cases: React.FC = ({ addressToQuery }) => { +const Cases: React.FC = ({ searchParamAddress }) => { const { page, order, filter } = useParams(); const [searchParams] = useSearchParams(); const location = useRootPath(); @@ -35,13 +35,13 @@ const Cases: React.FC = ({ addressToQuery }) => { const disputeSkip = casesPerPage * (pageNumber - 1); const decodedFilter = decodeURIFilter(filter ?? "all"); const { data: disputesData } = useMyCasesQuery( - addressToQuery, + searchParamAddress, disputeSkip, decodedFilter, order === "asc" ? OrderDirection.Asc : OrderDirection.Desc ); - const { data: userData } = useUserQuery(addressToQuery, decodedFilter); + const { data: userData } = useUserQuery(searchParamAddress, decodedFilter); const totalCases = userData?.user?.disputes.length; const totalResolvedCases = parseInt(userData?.user?.totalResolvedDisputes); const totalPages = useMemo( diff --git a/web/src/pages/Profile/JurorCard/BottomContent/JurorRewards.tsx b/web/src/pages/Profile/JurorCard/BottomContent/JurorRewards.tsx index 23261df92..96adeab1f 100644 --- a/web/src/pages/Profile/JurorCard/BottomContent/JurorRewards.tsx +++ b/web/src/pages/Profile/JurorCard/BottomContent/JurorRewards.tsx @@ -41,11 +41,11 @@ const tooltipMsg = "arbitration fees (ETH) + PNK redistribution between jurors."; interface IJurorRewards { - addressToQuery: `0x${string}`; + searchParamAddress: `0x${string}`; } -const JurorRewards: React.FC = ({ addressToQuery }) => { - const { data } = useUserQuery(addressToQuery); +const JurorRewards: React.FC = ({ searchParamAddress }) => { + const { data } = useUserQuery(searchParamAddress); const coinIds = [CoinIds.PNK, CoinIds.ETH]; const { prices: pricesData } = useCoinPrice(coinIds); diff --git a/web/src/pages/Profile/JurorCard/BottomContent/index.tsx b/web/src/pages/Profile/JurorCard/BottomContent/index.tsx index 4d49c13e9..34749232b 100644 --- a/web/src/pages/Profile/JurorCard/BottomContent/index.tsx +++ b/web/src/pages/Profile/JurorCard/BottomContent/index.tsx @@ -46,21 +46,21 @@ interface IBottomContent { userLevelData: ILevelCriteria; totalCoherentVotes: number; totalResolvedVotes: number; - addressToQuery: `0x${string}`; + searchParamAddress: `0x${string}`; } const BottomContent: React.FC = ({ userLevelData, totalCoherentVotes, totalResolvedVotes, - addressToQuery, + searchParamAddress, }) => { return ( - + diff --git a/web/src/pages/Profile/JurorCard/Header.tsx b/web/src/pages/Profile/JurorCard/Header.tsx index 13c4e88be..fed750f01 100644 --- a/web/src/pages/Profile/JurorCard/Header.tsx +++ b/web/src/pages/Profile/JurorCard/Header.tsx @@ -4,7 +4,6 @@ import styled from "styled-components"; import { responsiveSize } from "styles/responsiveSize"; import { useToggle } from "react-use"; -import { useSearchParams } from "react-router-dom"; import XIcon from "svgs/socialmedia/x.svg"; @@ -52,7 +51,7 @@ interface IHeader { levelNumber: number; totalCoherentVotes: number; totalResolvedVotes: number; - addressToQuery: `0x${string}`; + searchParamAddress: `0x${string}`; } const Header: React.FC = ({ @@ -60,16 +59,13 @@ const Header: React.FC = ({ levelNumber, totalCoherentVotes, totalResolvedVotes, - addressToQuery, + searchParamAddress, }) => { const [isJurorLevelsMiniGuideOpen, toggleJurorLevelsMiniGuide] = useToggle(false); - const [searchParams] = useSearchParams(); - const coherencePercentage = parseFloat(((totalCoherentVotes / Math.max(totalResolvedVotes, 1)) * 100).toFixed(2)); const courtUrl = window.location.origin; const xPostText = `Hey I've been busy as a Juror on the Kleros court, check out my score: \n\nLevel: ${levelNumber} (${levelTitle})\nCoherence Percentage: ${coherencePercentage}%\nCoherent Votes: ${totalCoherentVotes}/${totalResolvedVotes}\n\nBe a juror with me! ➡️ ${courtUrl}`; const xShareUrl = `https://twitter.com/intent/tweet?text=${encodeURIComponent(xPostText)}`; - const searchParamAddress = searchParams.get("address")?.toLowerCase(); return ( diff --git a/web/src/pages/Profile/JurorCard/index.tsx b/web/src/pages/Profile/JurorCard/index.tsx index 7ba721bad..2533fc09c 100644 --- a/web/src/pages/Profile/JurorCard/index.tsx +++ b/web/src/pages/Profile/JurorCard/index.tsx @@ -27,11 +27,11 @@ const Card = styled(_Card)` `; interface IJurorCard { - addressToQuery: `0x${string}`; + searchParamAddress: `0x${string}`; } -const JurorCard: React.FC = ({ addressToQuery }) => { - const { data } = useUserQuery(addressToQuery); +const JurorCard: React.FC = ({ searchParamAddress }) => { + const { data } = useUserQuery(searchParamAddress); const totalCoherentVotes = data?.user ? parseInt(data?.user?.totalCoherentVotes) : 0; const totalResolvedVotes = data?.user ? parseInt(data?.user?.totalResolvedVotes) : 0; const totalResolvedDisputes = data?.user ? parseInt(data?.user?.totalResolvedDisputes) : 0; @@ -43,12 +43,12 @@ const JurorCard: React.FC = ({ addressToQuery }) => {
- + - + ); diff --git a/web/src/pages/Profile/Stakes/index.tsx b/web/src/pages/Profile/Stakes/index.tsx index 5be1921f7..0ace54253 100644 --- a/web/src/pages/Profile/Stakes/index.tsx +++ b/web/src/pages/Profile/Stakes/index.tsx @@ -35,13 +35,13 @@ const StyledLabel = styled.label` `; interface IStakes { - addressToQuery: `0x${string}`; + searchParamAddress: `0x${string}`; } -const Stakes: React.FC = ({ addressToQuery }) => { - const { data: stakeData, isLoading } = useJurorStakeDetailsQuery(addressToQuery); +const Stakes: React.FC = ({ searchParamAddress }) => { + const { data: stakeData, isLoading } = useJurorStakeDetailsQuery(searchParamAddress); const { data: jurorBalance } = useReadSortitionModuleGetJurorBalance({ - args: [addressToQuery, BigInt(1)], + args: [searchParamAddress, BigInt(1)], }); const stakedCourts = stakeData?.jurorTokensPerCourts?.filter(({ staked }) => staked > 0); const isStaked = stakedCourts && stakedCourts.length > 0; diff --git a/web/src/pages/Profile/index.tsx b/web/src/pages/Profile/index.tsx index 8e7fc37b7..bc82ee79d 100644 --- a/web/src/pages/Profile/index.tsx +++ b/web/src/pages/Profile/index.tsx @@ -1,4 +1,4 @@ -import React from "react"; +import React, { useEffect } from "react"; import { Routes, Route, useNavigate, useSearchParams, useLocation, Navigate } from "react-router-dom"; import { useAccount } from "wagmi"; import styled, { css } from "styled-components"; @@ -69,7 +69,12 @@ const Profile: React.FC = () => { const { pathname } = useLocation(); const navigate = useNavigate(); const searchParamAddress = searchParams.get("address")?.toLowerCase(); - const addressToQuery = searchParamAddress || connectedAddress?.toLowerCase(); + + useEffect(() => { + if (isConnected && !searchParamAddress && connectedAddress) { + navigate(`${pathname}?address=${connectedAddress.toLowerCase()}`, { replace: true }); + } + }, [isConnected, searchParamAddress, connectedAddress, pathname, navigate]); const handleTabChange = (tabIndex: number) => { const selectedTab = TABS[tabIndex]; @@ -80,17 +85,17 @@ const Profile: React.FC = () => { return ( - {isConnected || searchParamAddress ? ( + {searchParamAddress ? ( <> - + handleTabChange(tabIndex)} /> - } /> - } /> + } /> + } /> } /> { /> - ) : ( + ) : !isConnected ? ( To see your profile, connect first
- )} + ) : null}
From 6197733a84d1b663ed41de9b4da80bd73590f9f6 Mon Sep 17 00:00:00 2001 From: kemuru <102478601+kemuru@users.noreply.github.com> Date: Fri, 7 Feb 2025 03:05:23 +0100 Subject: [PATCH 4/7] feat: add total stake info in stakes tab --- web/src/components/NumberDisplay.tsx | 4 +- web/src/pages/Profile/Stakes/Header.tsx | 71 +++++++++++++++++-------- web/src/pages/Profile/Stakes/index.tsx | 10 ++-- 3 files changed, 54 insertions(+), 31 deletions(-) diff --git a/web/src/components/NumberDisplay.tsx b/web/src/components/NumberDisplay.tsx index ee67ec43e..f501ec22b 100644 --- a/web/src/components/NumberDisplay.tsx +++ b/web/src/components/NumberDisplay.tsx @@ -1,6 +1,8 @@ import React from "react"; import { Tooltip } from "@kleros/ui-components-library"; + +import { commify } from "utils/commify"; interface INumberDisplay { value: string | number; unit?: string; @@ -31,7 +33,7 @@ const NumberDisplay: React.FC = ({ isCurrency = false, }) => { const parsedValue = Number(value); - const formattedValue = getFormattedValue(parsedValue, decimals); + const formattedValue = commify(getFormattedValue(parsedValue, decimals)); const tooltipValue = isCurrency ? `${unit} ${value}` : `${value} ${unit}`; const displayUnit = showUnitInDisplay ? unit : ""; const displayValue = isCurrency ? `${displayUnit} ${formattedValue}` : `${formattedValue} ${displayUnit}`; diff --git a/web/src/pages/Profile/Stakes/Header.tsx b/web/src/pages/Profile/Stakes/Header.tsx index 93c7f3dd9..5fbe0116e 100644 --- a/web/src/pages/Profile/Stakes/Header.tsx +++ b/web/src/pages/Profile/Stakes/Header.tsx @@ -1,15 +1,16 @@ import React from "react"; import styled, { css } from "styled-components"; +import { landscapeStyle } from "styles/landscapeStyle"; +import { responsiveSize } from "styles/responsiveSize"; + import { formatUnits } from "viem"; +import PnkIcon from "svgs/icons/pnk.svg"; import LockerIcon from "svgs/icons/locker.svg"; import { isUndefined } from "utils/index"; -import { landscapeStyle } from "styles/landscapeStyle"; -import { responsiveSize } from "styles/responsiveSize"; - import NumberDisplay from "components/NumberDisplay"; const Container = styled.div` @@ -28,17 +29,16 @@ const Container = styled.div` )} `; -const LockedPnk = styled.div` +const StakedPnk = styled.div` display: flex; - flex-wrap: nowrap; gap: 8px; - justify-content: flex-start; + align-items: center; +`; - ${landscapeStyle( - () => css` - align-self: center; - ` - )} +const LockedPnk = styled.div` + display: flex; + gap: 8px; + align-items: center; `; const StyledTitle = styled.h1` @@ -46,30 +46,55 @@ const StyledTitle = styled.h1` font-size: ${responsiveSize(20, 24)}; `; +const TotalStakeAndLockedPnk = styled.div` + display: flex; + flex-direction: row; + gap: 12px 24px; + flex-wrap: wrap; +`; + +const StyledPnkIcon = styled(PnkIcon)` + fill: ${({ theme }) => theme.secondaryPurple}; + width: 14px; +`; + const StyledLockerIcon = styled(LockerIcon)` fill: ${({ theme }) => theme.secondaryPurple}; width: 14px; `; interface IHeader { - lockedStake: bigint; + totalStake: string; + lockedStake: string; } -const Header: React.FC = ({ lockedStake }) => { - const formattedLockedStake = !isUndefined(lockedStake) && formatUnits(lockedStake, 18); +const Header: React.FC = ({ totalStake, lockedStake }) => { + const formattedTotalStake = formatUnits(BigInt(totalStake), 18); + const formattedLockedStake = formatUnits(BigInt(lockedStake), 18); return ( Stakes - {!isUndefined(lockedStake) ? ( - - - - - - - - ) : null} + + {!isUndefined(totalStake) ? ( + + + + + + + + ) : null} + {!isUndefined(lockedStake) ? ( + + + + + + + + ) : null} + ); }; diff --git a/web/src/pages/Profile/Stakes/index.tsx b/web/src/pages/Profile/Stakes/index.tsx index 0ace54253..331212b22 100644 --- a/web/src/pages/Profile/Stakes/index.tsx +++ b/web/src/pages/Profile/Stakes/index.tsx @@ -3,8 +3,6 @@ import styled, { css } from "styled-components"; import Skeleton from "react-loading-skeleton"; -import { useReadSortitionModuleGetJurorBalance } from "hooks/contracts/generated"; - import { useJurorStakeDetailsQuery } from "queries/useJurorStakeDetailsQuery"; import { landscapeStyle } from "styles/landscapeStyle"; @@ -40,16 +38,14 @@ interface IStakes { const Stakes: React.FC = ({ searchParamAddress }) => { const { data: stakeData, isLoading } = useJurorStakeDetailsQuery(searchParamAddress); - const { data: jurorBalance } = useReadSortitionModuleGetJurorBalance({ - args: [searchParamAddress, BigInt(1)], - }); const stakedCourts = stakeData?.jurorTokensPerCourts?.filter(({ staked }) => staked > 0); const isStaked = stakedCourts && stakedCourts.length > 0; - const lockedStake = jurorBalance?.[1]; + const totalStake = stakeData?.jurorTokensPerCourts?.[0]?.effectiveStake ?? "0"; + const lockedStake = stakeData?.jurorTokensPerCourts?.[0]?.locked ?? "0"; return ( -
+
{isLoading ? : null} {!isStaked && !isLoading ? No stakes found : null} {isStaked && !isLoading ? ( From 59b50e941bd344bcf1db6eebf00de0aeea459a7d Mon Sep 17 00:00:00 2001 From: kemuru <102478601+kemuru@users.noreply.github.com> Date: Fri, 7 Feb 2025 14:12:38 +0100 Subject: [PATCH 5/7] fix: commify fails when string contains < or >, responsiveness issues in mobile --- web/src/components/NumberDisplay.tsx | 7 ++++--- web/src/pages/Profile/Stakes/CourtCard/CourtName.tsx | 3 ++- web/src/pages/Profile/Stakes/Header.tsx | 2 +- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/web/src/components/NumberDisplay.tsx b/web/src/components/NumberDisplay.tsx index f501ec22b..351380c98 100644 --- a/web/src/components/NumberDisplay.tsx +++ b/web/src/components/NumberDisplay.tsx @@ -3,6 +3,7 @@ import React from "react"; import { Tooltip } from "@kleros/ui-components-library"; import { commify } from "utils/commify"; + interface INumberDisplay { value: string | number; unit?: string; @@ -21,7 +22,7 @@ const getFormattedValue = (value: number, decimals: number) => { return `> -0.${"0".repeat(decimals - 1)}1`; } } - return withFixedDecimals; + return commify(withFixedDecimals); }; const NumberDisplay: React.FC = ({ @@ -33,8 +34,8 @@ const NumberDisplay: React.FC = ({ isCurrency = false, }) => { const parsedValue = Number(value); - const formattedValue = commify(getFormattedValue(parsedValue, decimals)); - const tooltipValue = isCurrency ? `${unit} ${value}` : `${value} ${unit}`; + const formattedValue = getFormattedValue(parsedValue, decimals); + const tooltipValue = isCurrency ? `${unit} ${commify(value)}` : `${commify(value)} ${unit}`; const displayUnit = showUnitInDisplay ? unit : ""; const displayValue = isCurrency ? `${displayUnit} ${formattedValue}` : `${formattedValue} ${displayUnit}`; return ( diff --git a/web/src/pages/Profile/Stakes/CourtCard/CourtName.tsx b/web/src/pages/Profile/Stakes/CourtCard/CourtName.tsx index 6b53b480d..0f269b754 100644 --- a/web/src/pages/Profile/Stakes/CourtCard/CourtName.tsx +++ b/web/src/pages/Profile/Stakes/CourtCard/CourtName.tsx @@ -11,9 +11,10 @@ const Container = styled.div` display: flex; width: 100%; flex-direction: row; - gap: 16px; + gap: 8px 16px; align-items: center; justify-content: space-between; + flex-wrap: wrap; small { height: 100%; diff --git a/web/src/pages/Profile/Stakes/Header.tsx b/web/src/pages/Profile/Stakes/Header.tsx index 5fbe0116e..4fe7899e0 100644 --- a/web/src/pages/Profile/Stakes/Header.tsx +++ b/web/src/pages/Profile/Stakes/Header.tsx @@ -55,7 +55,7 @@ const TotalStakeAndLockedPnk = styled.div` const StyledPnkIcon = styled(PnkIcon)` fill: ${({ theme }) => theme.secondaryPurple}; - width: 14px; + width: 16px; `; const StyledLockerIcon = styled(LockerIcon)` From 6908af8338377879d5873ef5ee98922f4715a633 Mon Sep 17 00:00:00 2001 From: kemuru <102478601+kemuru@users.noreply.github.com> Date: Tue, 11 Feb 2025 04:37:11 +0100 Subject: [PATCH 6/7] feat: test in staging atlas, votes tab setup, pagination setup, stylings, staking history --- web/src/components/EvidenceCard.tsx | 2 +- web/src/components/JurorLink.tsx | 2 +- web/src/hooks/queries/useStakingHistory.ts | 62 ++++++++++++++ .../Settings/General/WalletAndProfile.tsx | 2 +- .../Voting/VotesDetails/AccordionTitle.tsx | 2 +- web/src/pages/Jurors/index.tsx | 2 +- .../Profile/Stakes/CourtCard/CourtName.tsx | 16 ---- .../pages/Profile/Stakes/CourtCard/Stake.tsx | 38 +-------- .../pages/Profile/Stakes/CourtCard/index.tsx | 78 ++++++++++++++++-- .../Stakes/{ => CurrentStakes}/Header.tsx | 4 +- .../Profile/Stakes/CurrentStakes/index.tsx | 56 +++++++++++++ .../pages/Profile/Stakes/StakingHistory.tsx | 81 +++++++++++++++++++ web/src/pages/Profile/Stakes/index.tsx | 43 ++++------ .../Profile/Votes/StatsAndFilters/Filters.tsx | 64 +++++++++++++++ .../Profile/Votes/StatsAndFilters/Stats.tsx | 55 +++++++++++++ .../Profile/Votes/StatsAndFilters/index.tsx | 24 ++++++ web/src/pages/Profile/Votes/index.tsx | 55 ++++++++++++- web/src/pages/Profile/index.tsx | 13 +-- web/src/utils/findCourtNameById.ts | 14 ++++ 19 files changed, 513 insertions(+), 100 deletions(-) create mode 100644 web/src/hooks/queries/useStakingHistory.ts rename web/src/pages/Profile/Stakes/{ => CurrentStakes}/Header.tsx (96%) create mode 100644 web/src/pages/Profile/Stakes/CurrentStakes/index.tsx create mode 100644 web/src/pages/Profile/Stakes/StakingHistory.tsx create mode 100644 web/src/pages/Profile/Votes/StatsAndFilters/Filters.tsx create mode 100644 web/src/pages/Profile/Votes/StatsAndFilters/Stats.tsx create mode 100644 web/src/pages/Profile/Votes/StatsAndFilters/index.tsx create mode 100644 web/src/utils/findCourtNameById.ts diff --git a/web/src/components/EvidenceCard.tsx b/web/src/components/EvidenceCard.tsx index 43e0e1d4b..ed09e4567 100644 --- a/web/src/components/EvidenceCard.tsx +++ b/web/src/components/EvidenceCard.tsx @@ -223,7 +223,7 @@ const EvidenceCard: React.FC = ({ description, fileURI, }) => { - const profileLink = `/profile/stakes?address=${sender}`; + const profileLink = `/profile/stakes/1?address=${sender}`; const transactionExplorerLink = useMemo(() => { return getTxnExplorerLink(transactionHash ?? ""); diff --git a/web/src/components/JurorLink.tsx b/web/src/components/JurorLink.tsx index cbb5e192a..072c530d3 100644 --- a/web/src/components/JurorLink.tsx +++ b/web/src/components/JurorLink.tsx @@ -50,7 +50,7 @@ const JurorLink: React.FC = ({ address, isInternalLink = true }) => const profileLink = isConnected && connectedAddress?.toLowerCase() === address.toLowerCase() ? "/profile" - : `/profile/stakes?address=${address}`; + : `/profile/stakes/1?address=${address}`; const addressExplorerLink = useMemo(() => { return `${getChain(DEFAULT_CHAIN)?.blockExplorers?.default.url}/address/${address}`; }, [address]); diff --git a/web/src/hooks/queries/useStakingHistory.ts b/web/src/hooks/queries/useStakingHistory.ts new file mode 100644 index 000000000..126c41bd1 --- /dev/null +++ b/web/src/hooks/queries/useStakingHistory.ts @@ -0,0 +1,62 @@ +import { useQuery } from "@tanstack/react-query"; + +// dynamic atlasUri would go here +const atlasUri = "https://url.example/graphql"; + +const AUTH_TOKEN = "Bearer tokenExampleGoesHere"; + +export const useStakingHistory = (take: number, lastCursorId?: number) => { + const variables = { + pagination: { take, lastCursorId: lastCursorId ?? null }, + }; + + return useQuery({ + queryKey: ["stakingHistoryQuery", take, lastCursorId], + enabled: true, + staleTime: 60000, + queryFn: async () => { + console.log("Fetching with variables:", variables); + + try { + const response = await fetch(atlasUri, { + method: "POST", + headers: { + "Content-Type": "application/json", + Authorization: AUTH_TOKEN, + }, + body: JSON.stringify({ + query: ` + query GetStakingEvents($pagination: PaginationArgs) { + userStakingEvents(pagination: $pagination) { + edges { + node { + name + args + blockTimestamp + transactionHash + } + cursor + } + count + hasNextPage + } + } + `, + variables, + }), + }); + + const result = await response.json(); + + if (!response.ok) { + throw new Error(`GraphQL error: ${JSON.stringify(result)}`); + } + + return result; + } catch (error) { + console.error("GraphQL Fetch Error:", error); + throw error; + } + }, + }); +}; diff --git a/web/src/layout/Header/navbar/Menu/Settings/General/WalletAndProfile.tsx b/web/src/layout/Header/navbar/Menu/Settings/General/WalletAndProfile.tsx index 16d9fe1f6..bf7f0e5b5 100644 --- a/web/src/layout/Header/navbar/Menu/Settings/General/WalletAndProfile.tsx +++ b/web/src/layout/Header/navbar/Menu/Settings/General/WalletAndProfile.tsx @@ -52,7 +52,7 @@ const WalletAndProfile: React.FC = ({ toggleIsSettingsOpen }) => { - + My Profile diff --git a/web/src/pages/Cases/CaseDetails/Voting/VotesDetails/AccordionTitle.tsx b/web/src/pages/Cases/CaseDetails/Voting/VotesDetails/AccordionTitle.tsx index 185e87e33..6d6de16c3 100644 --- a/web/src/pages/Cases/CaseDetails/Voting/VotesDetails/AccordionTitle.tsx +++ b/web/src/pages/Cases/CaseDetails/Voting/VotesDetails/AccordionTitle.tsx @@ -86,7 +86,7 @@ const AccordionTitle: React.FC<{ commited: boolean; hiddenVotes: boolean; }> = ({ juror, choice, voteCount, period, answers, isActiveRound, commited, hiddenVotes }) => { - const profileLink = `/profile/stakes?address=${juror}`; + const profileLink = `/profile/stakes/1?address=${juror}`; return ( diff --git a/web/src/pages/Jurors/index.tsx b/web/src/pages/Jurors/index.tsx index ff021af90..2b5fcc0fc 100644 --- a/web/src/pages/Jurors/index.tsx +++ b/web/src/pages/Jurors/index.tsx @@ -55,7 +55,7 @@ const Jurors: React.FC = () => {
Jurors Leaderboard {isConnected ? ( - + My Profile ) : null} diff --git a/web/src/pages/Profile/Stakes/CourtCard/CourtName.tsx b/web/src/pages/Profile/Stakes/CourtCard/CourtName.tsx index 0f269b754..443503dc9 100644 --- a/web/src/pages/Profile/Stakes/CourtCard/CourtName.tsx +++ b/web/src/pages/Profile/Stakes/CourtCard/CourtName.tsx @@ -3,10 +3,6 @@ import styled, { css } from "styled-components"; import { landscapeStyle } from "styles/landscapeStyle"; -import ArrowIcon from "svgs/icons/arrow.svg"; - -import { StyledArrowLink } from "components/StyledArrowLink"; - const Container = styled.div` display: flex; width: 100%; @@ -29,15 +25,6 @@ const Container = styled.div` )} `; -const ReStyledArrowLink = styled(StyledArrowLink)` - font-size: 14px; - - > svg { - height: 15px; - width: 15px; - } -`; - interface ICourtName { name: string; id: string; @@ -47,9 +34,6 @@ const CourtName: React.FC = ({ name, id }) => { return ( {name} - - Open Court - ); }; diff --git a/web/src/pages/Profile/Stakes/CourtCard/Stake.tsx b/web/src/pages/Profile/Stakes/CourtCard/Stake.tsx index c1617b621..168d30ff5 100644 --- a/web/src/pages/Profile/Stakes/CourtCard/Stake.tsx +++ b/web/src/pages/Profile/Stakes/CourtCard/Stake.tsx @@ -1,30 +1,10 @@ import React from "react"; -import styled, { css } from "styled-components"; +import styled from "styled-components"; import { formatUnits } from "viem"; -import { landscapeStyle } from "styles/landscapeStyle"; - import NumberDisplay from "components/NumberDisplay"; -import PnkIcon from "svgs/icons/pnk.svg"; - -const Container = styled.div` - display: flex; - flex-direction: row; - gap: 8px; - width: 100%; - justify-content: flex-start; - align-items: center; - - ${landscapeStyle( - () => css` - width: auto; - gap: 12px; - ` - )} -`; - const StyledLabel = styled.label` display: flex; font-weight: 600; @@ -34,13 +14,6 @@ const StyledLabel = styled.label` gap: 4px; `; -const StyledPnkIcon = styled(PnkIcon)` - display: inline-block; - width: 16px; - height: 16px; - fill: ${({ theme }) => theme.secondaryPurple}; -`; - interface IStake { stake: string; } @@ -49,12 +22,9 @@ const Stake: React.FC = ({ stake }) => { const formattedStake = formatUnits(stake, 18); return ( - - - - - - + + + ); }; export default Stake; diff --git a/web/src/pages/Profile/Stakes/CourtCard/index.tsx b/web/src/pages/Profile/Stakes/CourtCard/index.tsx index 360f97ca6..670a8ec92 100644 --- a/web/src/pages/Profile/Stakes/CourtCard/index.tsx +++ b/web/src/pages/Profile/Stakes/CourtCard/index.tsx @@ -1,14 +1,21 @@ import React from "react"; import styled, { css } from "styled-components"; +import { landscapeStyle } from "styles/landscapeStyle"; + import { Card as _Card } from "@kleros/ui-components-library"; -import { landscapeStyle } from "styles/landscapeStyle"; +import ArrowIcon from "svgs/icons/arrow.svg"; +import NewTabIcon from "svgs/icons/new-tab.svg"; +import { formatDate } from "utils/date"; +import { getTxnExplorerLink } from "utils/index"; + +import { StyledArrowLink } from "components/StyledArrowLink"; import CourtName from "./CourtName"; import Stake from "./Stake"; -const Container = styled(_Card)` +const Container = styled(_Card)<{ isCurrentStakeCard?: boolean }>` display: flex; flex-direction: row; align-items: center; @@ -16,7 +23,8 @@ const Container = styled(_Card)` height: auto; width: 100%; padding: 20px 16px 24px; - border-left: 5px solid ${({ theme }) => theme.secondaryPurple}; + border-left: 5px solid + ${({ theme, isCurrentStakeCard }) => (isCurrentStakeCard ? theme.secondaryPurple : theme.secondaryText)}; flex-wrap: wrap; gap: 16px; @@ -28,22 +36,76 @@ const Container = styled(_Card)` ${landscapeStyle( () => css` - padding: 21.5px 32px; + padding: 21.5px 28px; ` )} `; +const LeftContent = styled.div` + display: flex; + flex-direction: row; + flex-wrap: wrap; + gap: 16px 24px; +`; + +const StakeAndLinkAndDateContainer = styled.div` + display: flex; + flex-direction: row; + flex-wrap: wrap; + gap: 16px; +`; + +const StakeAndLink = styled.div` + display: flex; + flex-direction: row; + gap: 8px; +`; + +const ReStyledArrowLink = styled(StyledArrowLink)` + font-size: 14px; + + > svg { + height: 15px; + width: 15px; + } +`; + interface ICourtCard { name: string; stake: string; id: string; + timestamp?: number; + transactionHash?: string; + isCurrentStakeCard?: boolean; } -const CourtCard: React.FC = ({ name, stake, id }) => { +const CourtCard: React.FC = ({ + name, + stake, + id, + timestamp, + transactionHash, + isCurrentStakeCard = true, +}) => { return ( - - - + + + + + + + {transactionHash ? ( + + + + ) : null} + + {timestamp ? : null} + + + + Open Court + ); }; diff --git a/web/src/pages/Profile/Stakes/Header.tsx b/web/src/pages/Profile/Stakes/CurrentStakes/Header.tsx similarity index 96% rename from web/src/pages/Profile/Stakes/Header.tsx rename to web/src/pages/Profile/Stakes/CurrentStakes/Header.tsx index 4fe7899e0..1184f59a3 100644 --- a/web/src/pages/Profile/Stakes/Header.tsx +++ b/web/src/pages/Profile/Stakes/CurrentStakes/Header.tsx @@ -20,7 +20,7 @@ const Container = styled.div` width: 100%; gap: 4px 16px; align-items: center; - margin-bottom: ${responsiveSize(16, 24)}; + margin-bottom: 20px; ${landscapeStyle( () => css` @@ -74,7 +74,7 @@ const Header: React.FC = ({ totalStake, lockedStake }) => { return ( - Stakes + Current Stakes {!isUndefined(totalStake) ? ( diff --git a/web/src/pages/Profile/Stakes/CurrentStakes/index.tsx b/web/src/pages/Profile/Stakes/CurrentStakes/index.tsx new file mode 100644 index 000000000..c42428007 --- /dev/null +++ b/web/src/pages/Profile/Stakes/CurrentStakes/index.tsx @@ -0,0 +1,56 @@ +import React from "react"; +import styled from "styled-components"; + +import { responsiveSize } from "styles/responsiveSize"; + +import Skeleton from "react-loading-skeleton"; + +import { JurorStakeDetailsQuery } from "src/graphql/graphql"; + +import Header from "./Header"; +import CourtCard from "../CourtCard"; +import { CourtCardsContainer } from "../index"; + +const Container = styled.div` + display: flex; + flex-direction: column; + flex-wrap: wrap; +`; + +const NoCurrentStakesLabel = styled.label` + font-size: ${responsiveSize(14, 16)}; + margin-bottom: 12px; +`; + +interface ICurrentStakes { + totalStake: string; + lockedStake: string; + currentStakeData: JurorStakeDetailsQuery | undefined; + isLoading: boolean; +} + +const CurrentStakes: React.FC = ({ totalStake, lockedStake, currentStakeData, isLoading }) => { + const stakedCourts = currentStakeData?.jurorTokensPerCourts?.filter(({ staked }) => staked > 0); + const isStaked = stakedCourts && stakedCourts.length > 0; + + return ( + +
+ {!isStaked && !isLoading ? ( + No stakes found + ) : isLoading ? ( + + ) : null} + {isStaked && !isLoading ? ( + + {currentStakeData?.jurorTokensPerCourts + ?.filter(({ staked }) => staked > 0) + .map(({ court: { id, name }, staked }) => ( + + ))} + + ) : null} + + ); +}; +export default CurrentStakes; diff --git a/web/src/pages/Profile/Stakes/StakingHistory.tsx b/web/src/pages/Profile/Stakes/StakingHistory.tsx new file mode 100644 index 000000000..c6c1735bb --- /dev/null +++ b/web/src/pages/Profile/Stakes/StakingHistory.tsx @@ -0,0 +1,81 @@ +import React, { useMemo } from "react"; +import { useParams, useNavigate } from "react-router-dom"; +import styled from "styled-components"; +import { responsiveSize } from "styles/responsiveSize"; + +import Skeleton from "react-loading-skeleton"; + +import { useStakingHistory } from "queries/useStakingHistory"; +import { useCourtTree } from "queries/useCourtTree"; + +import { findCourtNameById } from "utils/findCourtNameById"; +import { StandardPagination } from "@kleros/ui-components-library"; + +import CourtCard from "./CourtCard"; +import { CourtCardsContainer } from "./index"; + +const Container = styled.div``; + +const StyledPagination = styled(StandardPagination)` + margin-top: 24px; + margin-left: auto; + margin-right: auto; +`; + +const StyledTitle = styled.h1` + font-size: ${responsiveSize(20, 24)}; + margin-bottom: 20px; +`; + +interface IStakingHistory { + searchParamAddress: `0x${string}`; + totalNumberStakingEvents: number; +} + +const StakingHistory: React.FC = ({ searchParamAddress, totalNumberStakingEvents }) => { + const { page } = useParams(); + const navigate = useNavigate(); + const eventsPerPage = 10; + const currentPage = parseInt(page ?? "1"); + const skip = (currentPage - 1) * eventsPerPage; + + const { data: stakingHistoryData, isLoading: isLoadingStakingHistory } = useStakingHistory(eventsPerPage, skip); + const { data: courtTreeData, isLoading: isLoadingCourtTree } = useCourtTree(); + const stakingEvents = stakingHistoryData?.data?.userStakingEvents?.edges ?? []; + const totalPages = useMemo(() => Math.ceil(totalNumberStakingEvents / eventsPerPage), [totalNumberStakingEvents]); + + const handlePageChange = (newPage: number) => { + navigate(`/profile/stakes/${newPage}?address=${searchParamAddress}`); + }; + + return ( + + Staking History + + {isLoadingStakingHistory || isLoadingCourtTree ? ( + Array.from({ length: 10 }).map((_, index) => ) + ) : ( + <> + {stakingEvents.map(({ node, cursor }) => { + const courtName = findCourtNameById(courtTreeData, node.args._courtID); + return ( + + ); + })} + + + )} + + + ); +}; + +export default StakingHistory; diff --git a/web/src/pages/Profile/Stakes/index.tsx b/web/src/pages/Profile/Stakes/index.tsx index 331212b22..b64ce4445 100644 --- a/web/src/pages/Profile/Stakes/index.tsx +++ b/web/src/pages/Profile/Stakes/index.tsx @@ -1,25 +1,28 @@ import React from "react"; import styled, { css } from "styled-components"; -import Skeleton from "react-loading-skeleton"; - +import { landscapeStyle } from "styles/landscapeStyle"; import { useJurorStakeDetailsQuery } from "queries/useJurorStakeDetailsQuery"; +import { useStakingHistory } from "queries/useStakingHistory"; -import { landscapeStyle } from "styles/landscapeStyle"; import { responsiveSize } from "styles/responsiveSize"; -import CourtCard from "./CourtCard"; -import Header from "./Header"; +import CurrentStakes from "./CurrentStakes"; +import StakingHistory from "./StakingHistory"; const Container = styled.div` + display: flex; + flex-direction: column; margin-top: ${responsiveSize(24, 32)}; + gap: 32px; `; -const CourtCardsContainer = styled.div` +export const CourtCardsContainer = styled.div` display: flex; flex-direction: column; gap: 12px; z-index: 0; + width: 100%; ${landscapeStyle( () => css` @@ -28,35 +31,21 @@ const CourtCardsContainer = styled.div` )} `; -const StyledLabel = styled.label` - font-size: ${responsiveSize(14, 16)}; -`; - interface IStakes { searchParamAddress: `0x${string}`; } const Stakes: React.FC = ({ searchParamAddress }) => { - const { data: stakeData, isLoading } = useJurorStakeDetailsQuery(searchParamAddress); - const stakedCourts = stakeData?.jurorTokensPerCourts?.filter(({ staked }) => staked > 0); - const isStaked = stakedCourts && stakedCourts.length > 0; - const totalStake = stakeData?.jurorTokensPerCourts?.[0]?.effectiveStake ?? "0"; - const lockedStake = stakeData?.jurorTokensPerCourts?.[0]?.locked ?? "0"; + const { data: currentStakeData, isLoading } = useJurorStakeDetailsQuery(searchParamAddress); + const { data: stakingHistoryData } = useStakingHistory(1, 0); + const totalStake = currentStakeData?.jurorTokensPerCourts?.[0]?.effectiveStake ?? "0"; + const lockedStake = currentStakeData?.jurorTokensPerCourts?.[0]?.locked ?? "0"; + const totalNumberStakingEvents = stakingHistoryData?.data?.userStakingEvents?.count ?? 0; return ( -
- {isLoading ? : null} - {!isStaked && !isLoading ? No stakes found : null} - {isStaked && !isLoading ? ( - - {stakeData?.jurorTokensPerCourts - ?.filter(({ staked }) => staked > 0) - .map(({ court: { id, name }, staked }) => ( - - ))} - - ) : null} + + ); }; diff --git a/web/src/pages/Profile/Votes/StatsAndFilters/Filters.tsx b/web/src/pages/Profile/Votes/StatsAndFilters/Filters.tsx new file mode 100644 index 000000000..f02abb785 --- /dev/null +++ b/web/src/pages/Profile/Votes/StatsAndFilters/Filters.tsx @@ -0,0 +1,64 @@ +import React from "react"; +import styled, { useTheme } from "styled-components"; + +import { useNavigate, useParams, useSearchParams } from "react-router-dom"; + +import { DropdownSelect } from "@kleros/ui-components-library"; + +import { decodeURIFilter, encodeURIFilter, useRootPath } from "utils/uri"; + +const Container = styled.div` + display: flex; + justify-content: end; + gap: 12px; + width: fit-content; +`; + +const Filters: React.FC = () => { + const theme = useTheme(); + const { order, filter } = useParams(); + const { ruled, period, ...filterObject } = decodeURIFilter(filter ?? "all"); + const navigate = useNavigate(); + const location = useRootPath(); + const [searchParams] = useSearchParams(); + + const handleStatusChange = (value: string | number) => { + const parsedValue = JSON.parse(value as string); + const encodedFilter = encodeURIFilter({ ...filterObject, ...parsedValue }); + navigate(`${location}/1/${order}/${encodedFilter}?${searchParams.toString()}`); + }; + + const handleOrderChange = (value: string | number) => { + const encodedFilter = encodeURIFilter({ ruled, period, ...filterObject }); + navigate(`${location}/1/${value}/${encodedFilter}?${searchParams.toString()}`); + }; + + return ( + + + + + ); +}; + +export default Filters; diff --git a/web/src/pages/Profile/Votes/StatsAndFilters/Stats.tsx b/web/src/pages/Profile/Votes/StatsAndFilters/Stats.tsx new file mode 100644 index 000000000..e64e292a5 --- /dev/null +++ b/web/src/pages/Profile/Votes/StatsAndFilters/Stats.tsx @@ -0,0 +1,55 @@ +import React from "react"; +import styled from "styled-components"; + +const FieldWrapper = styled.div` + display: inline-flex; + gap: 8px; +`; + +const SeparatorLabel = styled.label` + margin: 0 8px; + color: ${({ theme }) => theme.primaryText}; +`; + +const StyledLabel = styled.label` + color: ${({ theme }) => theme.primaryText}; +`; + +const Field: React.FC<{ label: string; value: string }> = ({ label, value }) => ( + + {label} + {value} + +); + +const Separator: React.FC = () => |; + +export interface IStats { + totalVotes: number; + votesPending: number; + resolvedVotes: number; +} + +const Stats: React.FC = ({ totalVotes, votesPending, resolvedVotes }) => { + const casesInProgress = (totalVotes - resolvedVotes).toString(); + + const fields = [ + { label: "Total", value: totalVotes.toString() }, + { label: "Vote Pending", value: votesPending }, + { label: "Case In Progress", value: casesInProgress }, + { label: "Resolved", value: resolvedVotes.toString() }, + ]; + + return ( +
+ {fields.map(({ label, value }, i) => ( + + + {i + 1 < fields.length ? : null} + + ))} +
+ ); +}; + +export default Stats; diff --git a/web/src/pages/Profile/Votes/StatsAndFilters/index.tsx b/web/src/pages/Profile/Votes/StatsAndFilters/index.tsx new file mode 100644 index 000000000..8291ccb6d --- /dev/null +++ b/web/src/pages/Profile/Votes/StatsAndFilters/index.tsx @@ -0,0 +1,24 @@ +import React from "react"; +import styled from "styled-components"; + +import { responsiveSize } from "styles/responsiveSize"; + +import Filters from "./Filters"; +import Stats, { IStats } from "./Stats"; + +const Container = styled.div` + display: flex; + flex-wrap: wrap; + gap: 8px; + margin-bottom: ${responsiveSize(16, 32)}; + justify-content: space-between; +`; + +const StatsAndFilters: React.FC = ({ totalVotes, votesPending, resolvedVotes }) => ( + + + + +); + +export default StatsAndFilters; diff --git a/web/src/pages/Profile/Votes/index.tsx b/web/src/pages/Profile/Votes/index.tsx index a93433eb2..1e869e11e 100644 --- a/web/src/pages/Profile/Votes/index.tsx +++ b/web/src/pages/Profile/Votes/index.tsx @@ -1,8 +1,57 @@ import React from "react"; +import styled from "styled-components"; -interface IVotes {} +import { responsiveSize } from "styles/responsiveSize"; -const Votes: React.FC = () => { - return
; +import { StandardPagination } from "@kleros/ui-components-library"; +import { useNavigate, useParams, useSearchParams } from "react-router-dom"; + +import { useRootPath } from "utils/uri"; + +import StatsAndFilters from "./StatsAndFilters"; + +const Container = styled.div` + display: flex; + flex-direction: column; + margin-top: ${responsiveSize(24, 32)}; + gap: 20px; +`; + +const StyledTitle = styled.h1` + margin-bottom: 0; + font-size: ${responsiveSize(20, 24)}; +`; + +const StyledPagination = styled(StandardPagination)` + margin-top: 24px; + margin-left: auto; + margin-right: auto; +`; + +interface IVotes { + searchParamAddress: `0x${string}`; +} + +const Votes: React.FC = ({ searchParamAddress }) => { + const { page, order, filter } = useParams(); + const navigate = useNavigate(); + const [searchParams] = useSearchParams(); + const votesPerPage = 5; + const location = useRootPath(); + const totalPages = 20; //TODO, HARDCODED FOR NOW + const currentPage = parseInt(page ?? "1"); + + const handlePageChange = (newPage: number) => { + navigate(`${location}/${newPage}/${order}/${filter}?${searchParams.toString()}`); + }; + + return ( + + Votes + + + + ); }; + export default Votes; diff --git a/web/src/pages/Profile/index.tsx b/web/src/pages/Profile/index.tsx index bc82ee79d..c9ba9b115 100644 --- a/web/src/pages/Profile/index.tsx +++ b/web/src/pages/Profile/index.tsx @@ -54,9 +54,9 @@ const ConnectWalletContainer = styled.div` `; const TABS = [ - { text: "Stakes", value: 0, Icon: PnkIcon, path: "stakes" }, + { text: "Stakes", value: 0, Icon: PnkIcon, path: "stakes/1" }, { text: "Cases", value: 1, Icon: DocIcon, path: "cases/1/desc/all" }, - { text: "Votes", value: 2, Icon: VotedIcon, path: "votes" }, + { text: "Votes", value: 2, Icon: VotedIcon, path: "votes/1/desc/all" }, ]; const getTabIndex = (currentPath: string) => { @@ -94,13 +94,16 @@ const Profile: React.FC = () => { callback={(tabIndex: number) => handleTabChange(tabIndex)} /> - } /> + } /> } /> - } /> + } /> + } /> diff --git a/web/src/utils/findCourtNameById.ts b/web/src/utils/findCourtNameById.ts new file mode 100644 index 000000000..b5cbadfdd --- /dev/null +++ b/web/src/utils/findCourtNameById.ts @@ -0,0 +1,14 @@ +import { CourtTreeQuery } from "src/graphql/graphql"; + +export const findCourtNameById = (courtTreeData: CourtTreeQuery, courtId: string) => { + const traverse = (court: CourtTreeQuery["court"]) => { + if (court.id === courtId) return court.name; + for (const child of court.children) { + const found = traverse(child); + if (found) return found; + } + return null; + }; + + return traverse(courtTreeData.court) ?? undefined; +}; From 4172322dbfd1aeb0eacf6bc383c2b1469c9630db Mon Sep 17 00:00:00 2001 From: kemuru <102478601+kemuru@users.noreply.github.com> Date: Wed, 12 Feb 2025 05:08:27 +0100 Subject: [PATCH 7/7] feat: build vote cards and stylings --- .../components/DisputeView/PeriodBanner.tsx | 3 +- .../Profile/Stakes/CurrentStakes/index.tsx | 16 ++-- .../pages/Profile/Stakes/StakingHistory.tsx | 9 ++- web/src/pages/Profile/Stakes/index.tsx | 4 +- .../Profile/Votes/StatsAndFilters/index.tsx | 2 +- .../Profile/Votes/VoteCard/CaseNumber.tsx | 45 +++++++++++ .../Profile/Votes/VoteCard/CaseStatus.tsx | 62 ++++++++++++++ .../Profile/Votes/VoteCard/CourtName.tsx | 39 +++++++++ .../pages/Profile/Votes/VoteCard/Round.tsx | 27 +++++++ web/src/pages/Profile/Votes/VoteCard/Vote.tsx | 39 +++++++++ .../pages/Profile/Votes/VoteCard/index.tsx | 80 +++++++++++++++++++ web/src/pages/Profile/Votes/index.tsx | 12 +++ 12 files changed, 326 insertions(+), 12 deletions(-) create mode 100644 web/src/pages/Profile/Votes/VoteCard/CaseNumber.tsx create mode 100644 web/src/pages/Profile/Votes/VoteCard/CaseStatus.tsx create mode 100644 web/src/pages/Profile/Votes/VoteCard/CourtName.tsx create mode 100644 web/src/pages/Profile/Votes/VoteCard/Round.tsx create mode 100644 web/src/pages/Profile/Votes/VoteCard/Vote.tsx create mode 100644 web/src/pages/Profile/Votes/VoteCard/index.tsx diff --git a/web/src/components/DisputeView/PeriodBanner.tsx b/web/src/components/DisputeView/PeriodBanner.tsx index 6a5287afa..1a120e1c6 100644 --- a/web/src/components/DisputeView/PeriodBanner.tsx +++ b/web/src/components/DisputeView/PeriodBanner.tsx @@ -58,13 +58,14 @@ const StyledLabel = styled.label<{ frontColor: string; withDot?: boolean; isCard ` : null} `; + export interface IPeriodBanner { id: number; period: Periods; isCard?: boolean; } -const getPeriodColors = (period: Periods, theme: Theme): [string, string] => { +export const getPeriodColors = (period: Periods, theme: Theme): [string, string] => { switch (period) { case Periods.appeal: return [theme.tint, theme.tintMedium]; diff --git a/web/src/pages/Profile/Stakes/CurrentStakes/index.tsx b/web/src/pages/Profile/Stakes/CurrentStakes/index.tsx index c42428007..4967c7818 100644 --- a/web/src/pages/Profile/Stakes/CurrentStakes/index.tsx +++ b/web/src/pages/Profile/Stakes/CurrentStakes/index.tsx @@ -19,29 +19,33 @@ const Container = styled.div` const NoCurrentStakesLabel = styled.label` font-size: ${responsiveSize(14, 16)}; - margin-bottom: 12px; `; interface ICurrentStakes { totalStake: string; lockedStake: string; currentStakeData: JurorStakeDetailsQuery | undefined; - isLoading: boolean; + isCurrentStakeLoading: boolean; } -const CurrentStakes: React.FC = ({ totalStake, lockedStake, currentStakeData, isLoading }) => { +const CurrentStakes: React.FC = ({ + totalStake, + lockedStake, + currentStakeData, + isCurrentStakeLoading, +}) => { const stakedCourts = currentStakeData?.jurorTokensPerCourts?.filter(({ staked }) => staked > 0); const isStaked = stakedCourts && stakedCourts.length > 0; return (
- {!isStaked && !isLoading ? ( + {!isStaked && !isCurrentStakeLoading ? ( No stakes found - ) : isLoading ? ( + ) : isCurrentStakeLoading ? ( ) : null} - {isStaked && !isLoading ? ( + {isStaked && !isCurrentStakeLoading ? ( {currentStakeData?.jurorTokensPerCourts ?.filter(({ staked }) => staked > 0) diff --git a/web/src/pages/Profile/Stakes/StakingHistory.tsx b/web/src/pages/Profile/Stakes/StakingHistory.tsx index c6c1735bb..e12d21bcf 100644 --- a/web/src/pages/Profile/Stakes/StakingHistory.tsx +++ b/web/src/pages/Profile/Stakes/StakingHistory.tsx @@ -27,6 +27,10 @@ const StyledTitle = styled.h1` margin-bottom: 20px; `; +const NoHistoryLabel = styled.label` + font-size: ${responsiveSize(14, 16)}; +`; + interface IStakingHistory { searchParamAddress: `0x${string}`; totalNumberStakingEvents: number; @@ -38,7 +42,6 @@ const StakingHistory: React.FC = ({ searchParamAddress, totalNu const eventsPerPage = 10; const currentPage = parseInt(page ?? "1"); const skip = (currentPage - 1) * eventsPerPage; - const { data: stakingHistoryData, isLoading: isLoadingStakingHistory } = useStakingHistory(eventsPerPage, skip); const { data: courtTreeData, isLoading: isLoadingCourtTree } = useCourtTree(); const stakingEvents = stakingHistoryData?.data?.userStakingEvents?.edges ?? []; @@ -52,7 +55,9 @@ const StakingHistory: React.FC = ({ searchParamAddress, totalNu Staking History - {isLoadingStakingHistory || isLoadingCourtTree ? ( + {!isLoadingStakingHistory && totalNumberStakingEvents === 0 ? ( + No history found + ) : isLoadingStakingHistory || isLoadingCourtTree ? ( Array.from({ length: 10 }).map((_, index) => ) ) : ( <> diff --git a/web/src/pages/Profile/Stakes/index.tsx b/web/src/pages/Profile/Stakes/index.tsx index b64ce4445..9984792bc 100644 --- a/web/src/pages/Profile/Stakes/index.tsx +++ b/web/src/pages/Profile/Stakes/index.tsx @@ -36,7 +36,7 @@ interface IStakes { } const Stakes: React.FC = ({ searchParamAddress }) => { - const { data: currentStakeData, isLoading } = useJurorStakeDetailsQuery(searchParamAddress); + const { data: currentStakeData, isLoading: isCurrentStakeLoading } = useJurorStakeDetailsQuery(searchParamAddress); const { data: stakingHistoryData } = useStakingHistory(1, 0); const totalStake = currentStakeData?.jurorTokensPerCourts?.[0]?.effectiveStake ?? "0"; const lockedStake = currentStakeData?.jurorTokensPerCourts?.[0]?.locked ?? "0"; @@ -44,7 +44,7 @@ const Stakes: React.FC = ({ searchParamAddress }) => { return ( - + ); diff --git a/web/src/pages/Profile/Votes/StatsAndFilters/index.tsx b/web/src/pages/Profile/Votes/StatsAndFilters/index.tsx index 8291ccb6d..970e4c05e 100644 --- a/web/src/pages/Profile/Votes/StatsAndFilters/index.tsx +++ b/web/src/pages/Profile/Votes/StatsAndFilters/index.tsx @@ -10,7 +10,7 @@ const Container = styled.div` display: flex; flex-wrap: wrap; gap: 8px; - margin-bottom: ${responsiveSize(16, 32)}; + margin-bottom: ${responsiveSize(4, 12)}; justify-content: space-between; `; diff --git a/web/src/pages/Profile/Votes/VoteCard/CaseNumber.tsx b/web/src/pages/Profile/Votes/VoteCard/CaseNumber.tsx new file mode 100644 index 000000000..c7f37e32e --- /dev/null +++ b/web/src/pages/Profile/Votes/VoteCard/CaseNumber.tsx @@ -0,0 +1,45 @@ +import React from "react"; +import styled, { css } from "styled-components"; + +import { landscapeStyle } from "styles/landscapeStyle"; + +import { InternalLink } from "components/InternalLink"; + +const Container = styled.div` + display: flex; + width: 100%; + flex-direction: row; + gap: 8px 16px; + align-items: center; + justify-content: space-between; + flex-wrap: wrap; + + small { + height: 100%; + font-weight: 600; + } + + ${landscapeStyle( + () => css` + justify-content: flex-start; + width: auto; + ` + )} +`; + +const StyledInternalLink = styled(InternalLink)` + font-weight: 600; +`; + +interface ICaseNumber { + id: string; +} + +const CaseNumber: React.FC = ({ id }) => { + return ( + + Case {id} + + ); +}; +export default CaseNumber; diff --git a/web/src/pages/Profile/Votes/VoteCard/CaseStatus.tsx b/web/src/pages/Profile/Votes/VoteCard/CaseStatus.tsx new file mode 100644 index 000000000..c1290cddc --- /dev/null +++ b/web/src/pages/Profile/Votes/VoteCard/CaseStatus.tsx @@ -0,0 +1,62 @@ +import React, { useMemo } from "react"; +import styled, { css, useTheme } from "styled-components"; + +import { Periods } from "consts/periods"; + +import { getPeriodColors } from "components/DisputeView/PeriodBanner"; + +interface ICaseStatus {} + +const StyledLabel = styled.label<{ frontColor: string; withDot?: boolean }>` + display: flex; + align-items: center; + width: auto; + color: ${({ frontColor }) => frontColor}; + ${({ withDot, frontColor }) => + withDot + ? css` + ::before { + content: ""; + display: inline-block; + height: 8px; + width: 8px; + border-radius: 50%; + margin-right: 8px; + background-color: ${frontColor}; + flex-shrink: 0; + } + ` + : null} +`; + +const getPeriodLabel = (period: Periods): string => { + switch (period) { + case Periods.evidence: + return "In Progress"; + case Periods.commit: + return "In Progress"; + case Periods.vote: + return "Voting"; + case Periods.appeal: + return "Crowdfunding Appeal"; + case Periods.execution: + return "Closed"; + default: + return "In Progress"; + } +}; + +const CaseStatus: React.FC = ({}) => { + const theme = useTheme(); + const [frontColor, backgroundColor] = useMemo( + () => getPeriodColors(Periods.evidence, theme), + [theme, Periods.evidence] + ); + + return ( + + {getPeriodLabel(Periods.evidence)} + + ); +}; +export default CaseStatus; diff --git a/web/src/pages/Profile/Votes/VoteCard/CourtName.tsx b/web/src/pages/Profile/Votes/VoteCard/CourtName.tsx new file mode 100644 index 000000000..74715c136 --- /dev/null +++ b/web/src/pages/Profile/Votes/VoteCard/CourtName.tsx @@ -0,0 +1,39 @@ +import React from "react"; +import styled, { css } from "styled-components"; + +import { landscapeStyle } from "styles/landscapeStyle"; + +const Container = styled.div` + display: flex; + width: 100%; + flex-direction: row; + gap: 8px 16px; + align-items: center; + justify-content: space-between; + flex-wrap: wrap; + + small { + height: 100%; + font-weight: 400; + } + + ${landscapeStyle( + () => css` + justify-content: flex-start; + width: auto; + ` + )} +`; + +interface ICourtName { + name: string; +} + +const CourtName: React.FC = ({ name }) => { + return ( + + {name} + + ); +}; +export default CourtName; diff --git a/web/src/pages/Profile/Votes/VoteCard/Round.tsx b/web/src/pages/Profile/Votes/VoteCard/Round.tsx new file mode 100644 index 000000000..7b0e4f40b --- /dev/null +++ b/web/src/pages/Profile/Votes/VoteCard/Round.tsx @@ -0,0 +1,27 @@ +import React from "react"; +import styled from "styled-components"; + +import RoundIcon from "svgs/icons/round.svg"; + +const Container = styled.div` + display: flex; + gap: 8px; + + small { + font-weight: 400; + } +`; + +interface IRound { + number: string; +} + +const Round: React.FC = ({ number }) => { + return ( + + + Round {number} + + ); +}; +export default Round; diff --git a/web/src/pages/Profile/Votes/VoteCard/Vote.tsx b/web/src/pages/Profile/Votes/VoteCard/Vote.tsx new file mode 100644 index 000000000..60fa5c431 --- /dev/null +++ b/web/src/pages/Profile/Votes/VoteCard/Vote.tsx @@ -0,0 +1,39 @@ +import React from "react"; +import styled from "styled-components"; + +import VotedIcon from "svgs/icons/voted-ballot.svg"; + +const Container = styled.div` + display: flex; + flex-direction: row; + gap: 8px; + + small { + font-weight: 400; + } +`; + +const StyledVotedIcon = styled(VotedIcon)` + path { + fill: ${({ theme }) => theme.primaryBlue}; + } +`; + +const BlueSmall = styled.small` + color: ${({ theme }) => theme.primaryBlue}; +`; + +interface IVote { + choice: string; +} + +const Vote: React.FC = ({ choice }) => { + return ( + + + Vote: + {choice} + + ); +}; +export default Vote; diff --git a/web/src/pages/Profile/Votes/VoteCard/index.tsx b/web/src/pages/Profile/Votes/VoteCard/index.tsx new file mode 100644 index 000000000..6761bee6c --- /dev/null +++ b/web/src/pages/Profile/Votes/VoteCard/index.tsx @@ -0,0 +1,80 @@ +import React from "react"; +import styled, { css } from "styled-components"; + +import { landscapeStyle } from "styles/landscapeStyle"; + +import { Card as _Card } from "@kleros/ui-components-library"; + +import ArrowIcon from "svgs/icons/arrow.svg"; + +import { StyledArrowLink } from "components/StyledArrowLink"; +import CourtName from "./CourtName"; +import CaseNumber from "./CaseNumber"; +import Vote from "./Vote"; +import Round from "./Round"; +import CaseStatus from "./CaseStatus"; + +const Container = styled(_Card)` + display: flex; + flex-direction: row; + align-items: center; + justify-content: space-between; + height: auto; + width: 100%; + padding: 20px 16px 24px; + border-left: 5px solid ${({ theme }) => theme.secondaryPurple}; + flex-wrap: wrap; + gap: 16px; + + :hover { + cursor: auto; + } + + ${({ theme }) => (theme.name === "light" ? `box-shadow: 0px 2px 3px 0px ${theme.stroke};` : "")} + + ${landscapeStyle( + () => css` + padding: 21.5px 28px; + ` + )} +`; + +const LeftContent = styled.div` + display: flex; + flex-direction: row; + flex-wrap: wrap; + gap: 16px 24px; +`; + +const ReStyledArrowLink = styled(StyledArrowLink)` + font-size: 14px; + + > svg { + height: 15px; + width: 15px; + } +`; + +interface IVoteCard {} + +const VoteCard: React.FC = ({}) => { + const courtName = "Technical Court"; + const caseId = "10"; + + return ( + + + + + + + + + + View vote + + + ); +}; + +export default VoteCard; diff --git a/web/src/pages/Profile/Votes/index.tsx b/web/src/pages/Profile/Votes/index.tsx index 1e869e11e..890dbe4da 100644 --- a/web/src/pages/Profile/Votes/index.tsx +++ b/web/src/pages/Profile/Votes/index.tsx @@ -9,6 +9,7 @@ import { useNavigate, useParams, useSearchParams } from "react-router-dom"; import { useRootPath } from "utils/uri"; import StatsAndFilters from "./StatsAndFilters"; +import VoteCard from "./VoteCard"; const Container = styled.div` display: flex; @@ -28,6 +29,12 @@ const StyledPagination = styled(StandardPagination)` margin-right: auto; `; +const VotesCardContainer = styled.div` + display: flex; + flex-direction: column; + gap: 8px; +`; + interface IVotes { searchParamAddress: `0x${string}`; } @@ -49,6 +56,11 @@ const Votes: React.FC = ({ searchParamAddress }) => { Votes + + {Array.from({ length: 5 }).map((_, index) => ( + + ))} + );