diff --git a/web/src/components/CasesDisplay/Filters.tsx b/web/src/components/CasesDisplay/Filters.tsx index cb417ac65..a8000de1e 100644 --- a/web/src/components/CasesDisplay/Filters.tsx +++ b/web/src/components/CasesDisplay/Filters.tsx @@ -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"; @@ -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(); diff --git a/web/src/components/CasesDisplay/Search.tsx b/web/src/components/CasesDisplay/Search.tsx index 390784c3c..6a4e9f69d 100644 --- a/web/src/components/CasesDisplay/Search.tsx +++ b/web/src/components/CasesDisplay/Search.tsx @@ -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"; @@ -39,6 +38,7 @@ const SearchBarContainer = styled.div` const StyledSearchbar = styled(Searchbar)` flex: 1; flex-basis: 310px; + input { font-size: 16px; height: 45px; @@ -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)) { @@ -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()}`); }} /> ) : ( diff --git a/web/src/components/EvidenceCard.tsx b/web/src/components/EvidenceCard.tsx index 7bb466804..3833316dc 100644 --- a/web/src/components/EvidenceCard.tsx +++ b/web/src/components/EvidenceCard.tsx @@ -139,7 +139,7 @@ const AccountContainer = styled.div` } `; -const HoverStyle = css` +const ExternalLinkHoverStyle = css` :hover { text-decoration: underline; color: ${({ theme }) => theme.primaryBlue}; @@ -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` @@ -221,9 +224,7 @@ const EvidenceCard: React.FC = ({ 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 ?? ""); @@ -248,9 +249,9 @@ const EvidenceCard: React.FC = ({ - +
{shortenAddress(sender)}
-
+
diff --git a/web/src/pages/Cases/CaseDetails/Voting/VotesDetails/AccordionTitle.tsx b/web/src/pages/Cases/CaseDetails/Voting/VotesDetails/AccordionTitle.tsx index b36e0a2a6..a8526ae0d 100644 --- a/web/src/pages/Cases/CaseDetails/Voting/VotesDetails/AccordionTitle.tsx +++ b/web/src/pages/Cases/CaseDetails/Voting/VotesDetails/AccordionTitle.tsx @@ -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; @@ -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}; } } `; @@ -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 ( - + {shortenAddress(juror)} - + diff --git a/web/src/pages/Dashboard/Courts/Header.tsx b/web/src/pages/Dashboard/Courts/Header.tsx index 2fb94c300..07f61e11c 100644 --- a/web/src/pages/Dashboard/Courts/Header.tsx +++ b/web/src/pages/Dashboard/Courts/Header.tsx @@ -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"; @@ -57,10 +58,12 @@ interface IHeader { const Header: React.FC = ({ lockedStake }) => { const formattedLockedStake = !isUndefined(lockedStake) && formatUnits(lockedStake, 18); + const [searchParams] = useSearchParams(); + const searchParamAddress = searchParams.get("address")?.toLowerCase(); return ( - My Courts + {searchParamAddress ? "Their" : "My"} Courts {!isUndefined(lockedStake) ? ( diff --git a/web/src/pages/Dashboard/Courts/index.tsx b/web/src/pages/Dashboard/Courts/index.tsx index c74dabfd4..512478b1d 100644 --- a/web/src/pages/Dashboard/Courts/index.tsx +++ b/web/src/pages/Dashboard/Courts/index.tsx @@ -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"; @@ -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 = ({ 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]; @@ -49,7 +54,9 @@ const Courts: React.FC = () => {
{isLoading ? : null} - {!isStaked && !isLoading ? You are not staked in any court : null} + {!isStaked && !isLoading ? ( + {searchParamAddress ? "They" : "You"} are not staked in any court + ) : null} {isStaked && !isLoading ? ( {stakeData?.jurorTokensPerCourts diff --git a/web/src/pages/Dashboard/JurorInfo/Header.tsx b/web/src/pages/Dashboard/JurorInfo/Header.tsx index 95c31597d..b6ba40cbc 100644 --- a/web/src/pages/Dashboard/JurorInfo/Header.tsx +++ b/web/src/pages/Dashboard/JurorInfo/Header.tsx @@ -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"; @@ -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 = ({ levelTitle, levelNumber, totalCoherentVotes, totalResolvedVotes }) => { +const Header: React.FC = ({ + 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 ( - Juror Dashboard + + Juror Dashboard - + + + {shortenAddress(addressToQuery)} + + + - {totalResolvedVotes > 0 ? ( + {totalResolvedVotes > 0 && !searchParamAddress ? ( Share your juror score diff --git a/web/src/pages/Dashboard/JurorInfo/JurorRewards.tsx b/web/src/pages/Dashboard/JurorInfo/JurorRewards.tsx index 21b800adf..40146345a 100644 --- a/web/src/pages/Dashboard/JurorInfo/JurorRewards.tsx +++ b/web/src/pages/Dashboard/JurorInfo/JurorRewards.tsx @@ -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 = ({ addressToQuery }) => { + const { data } = useUserQuery(addressToQuery); const coinIds = [CoinIds.PNK, CoinIds.ETH]; const { prices: pricesData } = useCoinPrice(coinIds); diff --git a/web/src/pages/Dashboard/JurorInfo/index.tsx b/web/src/pages/Dashboard/JurorInfo/index.tsx index 3e53053b5..501514683 100644 --- a/web/src/pages/Dashboard/JurorInfo/index.tsx +++ b/web/src/pages/Dashboard/JurorInfo/index.tsx @@ -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"; @@ -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 = ({ 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; @@ -55,12 +56,12 @@ const JurorInfo: React.FC = () => {
- - + + ); diff --git a/web/src/pages/Dashboard/index.tsx b/web/src/pages/Dashboard/index.tsx index bdf806288..8901f11e2 100644 --- a/web/src/pages/Dashboard/index.tsx +++ b/web/src/pages/Dashboard/index.tsx @@ -1,25 +1,22 @@ import React, { useMemo } from "react"; + import styled, { css } from "styled-components"; +import { MAX_WIDTH_LANDSCAPE, landscapeStyle } from "styles/landscapeStyle"; +import { responsiveSize } from "styles/responsiveSize"; -import { useNavigate, useParams } from "react-router-dom"; +import { useNavigate, useParams, useSearchParams } from "react-router-dom"; import { useAccount } from "wagmi"; 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 { MAX_WIDTH_LANDSCAPE, landscapeStyle } from "styles/landscapeStyle"; -import { responsiveSize } from "styles/responsiveSize"; - 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"; @@ -54,24 +51,26 @@ const ConnectWalletContainer = styled.div` `; const Dashboard: React.FC = () => { - const { isConnected, address } = useAccount(); + const { isConnected, address: connectedAddress } = useAccount(); const { page, order, filter } = useParams(); + const [searchParams] = useSearchParams(); const location = useRootPath(); 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( - address, + addressToQuery, disputeSkip, decodedFilter, order === "asc" ? OrderDirection.Asc : OrderDirection.Desc ); - const { data: userData } = useUserQuery(address, decodedFilter); + 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] @@ -79,18 +78,20 @@ const Dashboard: React.FC = () => { return ( - {isConnected ? ( + {isConnected || searchParamAddress ? ( <> - - + + navigate(`${location}/${newPage}/${order}/${filter}`)} + setCurrentPage={(newPage: number) => + navigate(`${location}/${newPage}/${order}/${filter}?${searchParams.toString()}`) + } {...{ casesPerPage }} /> diff --git a/web/src/pages/Home/TopJurors/JurorCard/JurorTitle.tsx b/web/src/pages/Home/TopJurors/JurorCard/JurorTitle.tsx index a60ae994e..24d39fe76 100644 --- a/web/src/pages/Home/TopJurors/JurorCard/JurorTitle.tsx +++ b/web/src/pages/Home/TopJurors/JurorCard/JurorTitle.tsx @@ -1,10 +1,8 @@ -import React, { useMemo } from "react"; +import React from "react"; import styled from "styled-components"; -import { DEFAULT_CHAIN, getChain } from "consts/chains"; - import { IdenticonOrAvatar, AddressOrName } from "components/ConnectWallet/AccountDisplay"; -import { ExternalLink } from "components/ExternalLink"; +import { InternalLink } from "components/InternalLink"; const Container = styled.div` display: flex; @@ -22,11 +20,11 @@ const Container = styled.div` } `; -const StyledExternalLink = styled(ExternalLink)` +const StyledInternalLink = styled(InternalLink)` :hover { label { cursor: pointer; - color: ${({ theme }) => theme.primaryBlue}; + color: ${({ theme }) => theme.secondaryBlue}; } } `; @@ -36,16 +34,14 @@ interface IJurorTitle { } const JurorTitle: React.FC = ({ address }) => { - const addressExplorerLink = useMemo(() => { - return `${getChain(DEFAULT_CHAIN)?.blockExplorers?.default.url}/address/${address}`; - }, [address]); + const dashboardLink = `/dashboard/1/desc/all?address=${address}`; return ( - + - + ); };