Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: public dashboards #1819

Merged
merged 2 commits into from
Jan 7, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions web/src/components/CasesDisplay/Filters.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import styled, { css, useTheme } from "styled-components";

import { hoverShortTransitionTiming } from "styles/commonStyles";

import { useNavigate, useParams } from "react-router-dom";
import { useNavigate, useParams, useSearchParams } from "react-router-dom";

import { DropdownSelect } from "@kleros/ui-components-library";

Expand Down Expand Up @@ -55,16 +55,17 @@ const Filters: React.FC = () => {
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}`);
navigate(`${location}/1/${order}/${encodedFilter}?${searchParams.toString()}`);
};

const handleOrderChange = (value: string | number) => {
const encodedFilter = encodeURIFilter({ ruled, period, ...filterObject });
navigate(`${location}/1/${value}/${encodedFilter}`);
navigate(`${location}/1/${value}/${encodedFilter}?${searchParams.toString()}`);
};

const { isList, setIsList } = useIsList();
Expand Down
19 changes: 14 additions & 5 deletions web/src/components/CasesDisplay/Search.tsx
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
import React, { useMemo, useState } from "react";
import React, { useMemo, useRef, useState } from "react";
import styled, { css } from "styled-components";

import Skeleton from "react-loading-skeleton";
import { useNavigate, useParams } from "react-router-dom";
import { useNavigate, useParams, useSearchParams } from "react-router-dom";
import { useDebounce } from "react-use";

import { Searchbar, DropdownCascader } from "@kleros/ui-components-library";

import { isEmpty, isUndefined } from "utils/index";
Expand Down Expand Up @@ -39,6 +38,7 @@ const SearchBarContainer = styled.div`
const StyledSearchbar = styled(Searchbar)`
flex: 1;
flex-basis: 310px;

input {
font-size: 16px;
height: 45px;
Expand All @@ -53,16 +53,25 @@ const Search: React.FC = () => {
const decodedFilter = decodeURIFilter(filter ?? "all");
const { id: searchValue, ...filterObject } = decodedFilter;
const [search, setSearch] = useState(searchValue ?? "");
const initialRenderRef = useRef(true);
const navigate = useNavigate();
const [searchParams] = useSearchParams();

useDebounce(
() => {
if (initialRenderRef.current && isEmpty(search)) {
initialRenderRef.current = false;
return;
}
initialRenderRef.current = false;
const newFilters = isEmpty(search) ? { ...filterObject } : { ...filterObject, id: search };
const encodedFilter = encodeURIFilter(newFilters);
navigate(`${location}/${page}/${order}/${encodedFilter}`);
navigate(`${location}/${page}/${order}/${encodedFilter}?${searchParams.toString()}`);
},
500,
[search]
);

const { data: courtTreeData } = useCourtTree();
const items = useMemo(() => {
if (!isUndefined(courtTreeData)) {
Expand All @@ -82,7 +91,7 @@ const Search: React.FC = () => {
onSelect={(value) => {
const { court: _, ...filterWithoutCourt } = decodedFilter;
const newFilter = value === "all" ? filterWithoutCourt : { ...decodedFilter, court: value.toString() };
navigate(`${location}/${page}/${order}/${encodeURIFilter(newFilter)}`);
navigate(`${location}/${page}/${order}/${encodeURIFilter(newFilter)}?${searchParams.toString()}`);
}}
/>
) : (
Expand Down
17 changes: 9 additions & 8 deletions web/src/components/EvidenceCard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,7 @@ const AccountContainer = styled.div`
}
`;

const HoverStyle = css`
const ExternalLinkHoverStyle = css`
:hover {
text-decoration: underline;
color: ${({ theme }) => theme.primaryBlue};
Expand All @@ -155,12 +155,15 @@ const HoverStyle = css`
`;

const Address = styled.p`
${HoverStyle}
margin: 0;

:hover {
color: ${({ theme }) => theme.secondaryBlue};
}
`;

const StyledExternalLink = styled(ExternalLink)`
${HoverStyle}
${ExternalLinkHoverStyle}
`;

const DesktopText = styled.span`
Expand Down Expand Up @@ -221,9 +224,7 @@ const EvidenceCard: React.FC<IEvidenceCard> = ({
description,
fileURI,
}) => {
const addressExplorerLink = useMemo(() => {
return `${getChain(DEFAULT_CHAIN)?.blockExplorers?.default.url}/address/${sender}`;
}, [sender]);
const dashboardLink = `/dashboard/1/desc/all?address=${sender}`;

const transactionExplorerLink = useMemo(() => {
return getTxnExplorerLink(transactionHash ?? "");
Expand All @@ -248,9 +249,9 @@ const EvidenceCard: React.FC<IEvidenceCard> = ({
<BottomLeftContent>
<AccountContainer>
<Identicon size="24" string={sender} />
<ExternalLink to={addressExplorerLink} rel="noopener noreferrer" target="_blank">
<InternalLink to={dashboardLink}>
<Address>{shortenAddress(sender)}</Address>
</ExternalLink>
</InternalLink>
</AccountContainer>
<StyledExternalLink to={transactionExplorerLink} rel="noopener noreferrer" target="_blank">
<label>{formatDate(Number(timestamp), true)}</label>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,16 @@
import React, { useMemo } from "react";
import React from "react";
import styled, { css } from "styled-components";

import { landscapeStyle } from "styles/landscapeStyle";

import Identicon from "react-identicons";

import { Answer } from "context/NewDisputeContext";
import { DEFAULT_CHAIN, getChain } from "consts/chains";
import { getVoteChoice } from "utils/getVoteChoice";
import { isUndefined } from "utils/index";
import { shortenAddress } from "utils/shortenAddress";

import { landscapeStyle } from "styles/landscapeStyle";
import { InternalLink } from "components/InternalLink";

const TitleContainer = styled.div`
display: flex;
Expand Down Expand Up @@ -41,12 +42,11 @@ const StyledSmall = styled.small`
font-size: 16px;
`;

const StyledA = styled.a`
const StyledInternalLink = styled(InternalLink)`
:hover {
text-decoration: underline;
label {
cursor: pointer;
color: ${({ theme }) => theme.primaryBlue};
color: ${({ theme }) => theme.secondaryBlue};
}
}
`;
Expand Down Expand Up @@ -88,17 +88,15 @@ const AccordionTitle: React.FC<{
commited: boolean;
hiddenVotes: boolean;
}> = ({ juror, choice, voteCount, period, answers, isActiveRound, commited, hiddenVotes }) => {
const addressExplorerLink = useMemo(() => {
return `${getChain(DEFAULT_CHAIN)?.blockExplorers?.default.url}/address/${juror}`;
}, [juror]);
const dashboardLink = `/dashboard/1/desc/all?address=${juror}`;

return (
<TitleContainer>
<AddressContainer>
<Identicon size="20" string={juror} />
<StyledA href={addressExplorerLink} rel="noopener noreferrer" target="_blank">
<StyledInternalLink to={dashboardLink}>
<StyledLabel variant="secondaryText">{shortenAddress(juror)}</StyledLabel>
</StyledA>
</StyledInternalLink>
</AddressContainer>
<VoteStatus {...{ choice, period, answers, isActiveRound, commited, hiddenVotes }} />
<StyledLabel variant="secondaryPurple">
Expand Down
5 changes: 4 additions & 1 deletion web/src/pages/Dashboard/Courts/Header.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ 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";

Expand Down Expand Up @@ -57,10 +58,12 @@ interface IHeader {

const Header: React.FC<IHeader> = ({ lockedStake }) => {
const formattedLockedStake = !isUndefined(lockedStake) && formatUnits(lockedStake, 18);
const [searchParams] = useSearchParams();
const searchParamAddress = searchParams.get("address")?.toLowerCase();

return (
<Container>
<StyledTitle>My Courts</StyledTitle>
<StyledTitle>{searchParamAddress ? "Their" : "My"} Courts</StyledTitle>
{!isUndefined(lockedStake) ? (
<LockedPnk>
<StyledLockerIcon />
Expand Down
19 changes: 13 additions & 6 deletions web/src/pages/Dashboard/Courts/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React from "react";
import styled, { css } from "styled-components";

import Skeleton from "react-loading-skeleton";
import { useAccount } from "wagmi";
import { useSearchParams } from "react-router-dom";

import { useReadSortitionModuleGetJurorBalance } from "hooks/contracts/generated";

Expand Down Expand Up @@ -35,12 +35,17 @@ const StyledLabel = styled.label`
font-size: ${responsiveSize(14, 16)};
`;

const Courts: React.FC = () => {
const { address } = useAccount();
const { data: stakeData, isLoading } = useJurorStakeDetailsQuery(address?.toLowerCase() as `0x${string}`);
interface ICourts {
addressToQuery: `0x${string}`;
}

const Courts: React.FC<ICourts> = ({ addressToQuery }) => {
const { data: stakeData, isLoading } = useJurorStakeDetailsQuery(addressToQuery);
const { data: jurorBalance } = useReadSortitionModuleGetJurorBalance({
args: [address as `0x${string}`, BigInt(1)],
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];
Expand All @@ -49,7 +54,9 @@ const Courts: React.FC = () => {
<Container>
<Header lockedStake={lockedStake ?? BigInt(0)} />
{isLoading ? <Skeleton /> : null}
{!isStaked && !isLoading ? <StyledLabel>You are not staked in any court</StyledLabel> : null}
{!isStaked && !isLoading ? (
<StyledLabel>{searchParamAddress ? "They" : "You"} are not staked in any court</StyledLabel>
) : null}
{isStaked && !isLoading ? (
<CourtCardsContainer>
{stakeData?.jurorTokensPerCourts
Expand Down
39 changes: 35 additions & 4 deletions web/src/pages/Dashboard/JurorInfo/Header.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
import React from "react";
import React, { useMemo } 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";
Expand Down Expand Up @@ -45,31 +50,57 @@ 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;
totalCoherentVotes: number;
totalResolvedVotes: number;
addressToQuery: `0x${string}`;
}

const Header: React.FC<IHeader> = ({ levelTitle, levelNumber, totalCoherentVotes, totalResolvedVotes }) => {
const Header: React.FC<IHeader> = ({
levelTitle,
levelNumber,
totalCoherentVotes,
totalResolvedVotes,
addressToQuery,
}) => {
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();

const addressExplorerLink = useMemo(() => {
return `${getChain(DEFAULT_CHAIN)?.blockExplorers?.default.url}/address/${addressToQuery}`;
}, [addressToQuery]);

return (
<Container>
<StyledTitle>Juror Dashboard</StyledTitle>
<StyledTitle>
Juror Dashboard -
<Copiable copiableContent={addressToQuery} info="Copy Address">
<StyledExternalLink to={addressExplorerLink} target="_blank" rel="noopener noreferrer">
{shortenAddress(addressToQuery)}
</StyledExternalLink>
</Copiable>
</StyledTitle>
<LinksContainer>
<HowItWorks
isMiniGuideOpen={isJurorLevelsMiniGuideOpen}
toggleMiniGuide={toggleJurorLevelsMiniGuide}
MiniGuideComponent={JurorLevels}
/>
{totalResolvedVotes > 0 ? (
{totalResolvedVotes > 0 && !searchParamAddress ? (
<StyledLink to={xShareUrl} target="_blank" rel="noreferrer">
<StyledXIcon /> <span>Share your juror score</span>
</StyledLink>
Expand Down
9 changes: 6 additions & 3 deletions web/src/pages/Dashboard/JurorInfo/JurorRewards.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,12 @@ const tooltipMsg =
"is coherent with the final ruling receive the Juror Rewards composed of " +
"arbitration fees (ETH) + PNK redistribution between jurors.";

const JurorRewards: React.FC = () => {
const { address } = useAccount();
const { data } = useUserQuery(address?.toLowerCase() as `0x${string}`);
interface IJurorRewards {
addressToQuery: `0x${string}`;
}

const JurorRewards: React.FC<IJurorRewards> = ({ addressToQuery }) => {
const { data } = useUserQuery(addressToQuery);
const coinIds = [CoinIds.PNK, CoinIds.ETH];
const { prices: pricesData } = useCoinPrice(coinIds);

Expand Down
17 changes: 9 additions & 8 deletions web/src/pages/Dashboard/JurorInfo/index.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import React from "react";
import styled, { css } from "styled-components";

import { useAccount } from "wagmi";

import { Card as _Card } from "@kleros/ui-components-library";

import { getUserLevelData } from "utils/userLevelCalculation";
Expand Down Expand Up @@ -39,9 +37,12 @@ const Card = styled(_Card)`
)}
`;

const JurorInfo: React.FC = () => {
const { address } = useAccount();
const { data } = useUserQuery(address?.toLowerCase() as `0x${string}`);
interface IJurorInfo {
addressToQuery: `0x${string}`;
}

const JurorInfo: React.FC<IJurorInfo> = ({ addressToQuery }) => {
const { data } = useUserQuery(addressToQuery);
// TODO check graph schema
const coherenceScore = data?.user ? parseInt(data?.user?.coherenceScore) : 0;
const totalCoherentVotes = data?.user ? parseInt(data?.user?.totalCoherentVotes) : 0;
Expand All @@ -55,12 +56,12 @@ const JurorInfo: React.FC = () => {
<Header
levelTitle={userLevelData.title}
levelNumber={userLevelData.level}
{...{ totalCoherentVotes, totalResolvedVotes }}
{...{ totalCoherentVotes, totalResolvedVotes, addressToQuery }}
/>
<Card>
<PixelArt level={userLevelData.level} width="189px" height="189px" />
<Coherence userLevelData={userLevelData} isMiniGuide={false} {...{ totalCoherentVotes, totalResolvedVotes }} />
<JurorRewards />
<Coherence isMiniGuide={false} {...{ userLevelData, totalCoherentVotes, totalResolvedVotes }} />
<JurorRewards {...{ addressToQuery }} />
</Card>
</Container>
);
Expand Down
Loading
Loading