From 410428c21ff2e6f6b515df4d933e3ccb65bf0744 Mon Sep 17 00:00:00 2001 From: alcercu <333aleix333@gmail.com> Date: Fri, 18 Aug 2023 16:10:59 +0200 Subject: [PATCH 01/36] chore(web): make skeleton style global --- web/src/components/DisputeCard/index.tsx | 4 ---- web/src/styles/global-style.ts | 6 ++++++ 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/web/src/components/DisputeCard/index.tsx b/web/src/components/DisputeCard/index.tsx index 5470894c3..aff25709f 100644 --- a/web/src/components/DisputeCard/index.tsx +++ b/web/src/components/DisputeCard/index.tsx @@ -17,10 +17,6 @@ const StyledCard = styled(Card)` min-width: 312px; width: auto; height: 260px; - - .react-loading-skeleton { - z-index: 0; - } `; const Container = styled.div` diff --git a/web/src/styles/global-style.ts b/web/src/styles/global-style.ts index b8a831311..a3ff246ea 100644 --- a/web/src/styles/global-style.ts +++ b/web/src/styles/global-style.ts @@ -8,6 +8,12 @@ export const GlobalStyle = createGlobalStyle` --toastify-color-error: ${({ theme }) => theme.error}; } + .react-loading-skeleton { + z-index: 0; + --highlight-color: ${({ theme }) => theme.stroke}; + --base-color: ${({ theme }) => theme.lightGrey}; + } + body { font-family: "Open Sans", sans-serif; margin: 0px; From f0264efa50ff8cc27aec548780e8f9f6617e3a9a Mon Sep 17 00:00:00 2001 From: alcercu <333aleix333@gmail.com> Date: Fri, 18 Aug 2023 16:12:25 +0200 Subject: [PATCH 02/36] feat(web): modularize cases query and add My cases section to dashboard --- web/src/components/CasesDisplay/CasesGrid.tsx | 31 ++++---- web/src/components/CasesDisplay/Stats.tsx | 16 ++-- .../CasesDisplay/StatsAndFilters.tsx | 6 +- web/src/components/CasesDisplay/index.tsx | 26 +++---- web/src/components/DisputeCard/index.tsx | 10 +-- web/src/hooks/queries/useCasesQuery.ts | 74 +++++++++++++------ web/src/hooks/queries/useCounter.ts | 20 +++++ web/src/hooks/queries/useUser.ts | 7 +- web/src/pages/Cases/index.tsx | 17 +++-- web/src/pages/Dashboard/index.tsx | 23 +++--- 10 files changed, 139 insertions(+), 91 deletions(-) create mode 100644 web/src/hooks/queries/useCounter.ts diff --git a/web/src/components/CasesDisplay/CasesGrid.tsx b/web/src/components/CasesDisplay/CasesGrid.tsx index a552db8fb..c5d1a593a 100644 --- a/web/src/components/CasesDisplay/CasesGrid.tsx +++ b/web/src/components/CasesDisplay/CasesGrid.tsx @@ -1,7 +1,9 @@ import React from "react"; import styled from "styled-components"; +import Skeleton from "react-loading-skeleton"; import { StandardPagination } from "@kleros/ui-components-library"; -import { CasesPageQuery } from "queries/useCasesQuery"; +import { isUndefined } from "utils/index"; +import { DisputeDetailsFragment } from "queries/useCasesQuery"; import DisputeCard from "components/DisputeCard"; const Container = styled.div` @@ -11,6 +13,11 @@ const Container = styled.div` gap: 8px; `; +const StyledSkeleton = styled(Skeleton)` + height: 260px; + width: 310px; +`; + // 24px as margin-top since we already have 8px from the flex gap const StyledPagination = styled(StandardPagination)` margin-top: 24px; @@ -19,30 +26,26 @@ const StyledPagination = styled(StandardPagination)` `; export interface ICasesGrid { - disputes: CasesPageQuery["disputes"]; + disputes?: DisputeDetailsFragment[]; currentPage: number; setCurrentPage: (newPage: number) => void; - numberDisputes: number; + numberDisputes?: number; casesPerPage: number; } -const CasesGrid: React.FC = ({ - disputes, - currentPage, - setCurrentPage, - numberDisputes, - casesPerPage, -}) => { +const CasesGrid: React.FC = ({ disputes, currentPage, setCurrentPage, numberDisputes, casesPerPage }) => { return ( <> - {disputes.map((dispute, i) => { - return ; - })} + {isUndefined(disputes) + ? [...Array(casesPerPage)].map((_, i) => ) + : disputes.map((dispute, i) => { + return ; + })} setCurrentPage(page)} /> diff --git a/web/src/components/CasesDisplay/Stats.tsx b/web/src/components/CasesDisplay/Stats.tsx index e877af7ef..6b49ca70a 100644 --- a/web/src/components/CasesDisplay/Stats.tsx +++ b/web/src/components/CasesDisplay/Stats.tsx @@ -1,6 +1,5 @@ import React from "react"; import styled from "styled-components"; -import { useAllCasesQuery } from "hooks/queries/useAllCasesQuery"; const FieldWrapper = styled.div` display: inline-flex; @@ -21,17 +20,18 @@ const SeparatorLabel = styled.label` const Separator: React.FC = () => |; -const Stats: React.FC = () => { - const { data } = useAllCasesQuery(); +export interface IStats { + totalDisputes: number; + closedDisputes: number; +} - const totalDisputes = data?.counter?.cases; - const closedDisputes = data?.counter?.casesRuled; +const Stats: React.FC = ({ totalDisputes, closedDisputes }) => { const inProgressDisputes = (totalDisputes - closedDisputes).toString(); const fields = [ - { label: "Total", value: totalDisputes ?? "0" }, - { label: "In Progress", value: inProgressDisputes ?? "0" }, - { label: "Closed", value: closedDisputes ?? "0" }, + { label: "Total", value: totalDisputes.toString() }, + { label: "In Progress", value: inProgressDisputes }, + { label: "Closed", value: closedDisputes.toString() }, ]; return ( diff --git a/web/src/components/CasesDisplay/StatsAndFilters.tsx b/web/src/components/CasesDisplay/StatsAndFilters.tsx index e2e9496bf..3d878aeb7 100644 --- a/web/src/components/CasesDisplay/StatsAndFilters.tsx +++ b/web/src/components/CasesDisplay/StatsAndFilters.tsx @@ -1,7 +1,7 @@ import React from "react"; import styled from "styled-components"; import Filters from "./Filters"; -import Stats from "./Stats"; +import Stats, { IStats } from "./Stats"; const Container = styled.div` display: flex; @@ -10,9 +10,9 @@ const Container = styled.div` margin-top: 8px; `; -const StatsAndFilters: React.FC = () => ( +const StatsAndFilters: React.FC = ({ totalDisputes, closedDisputes }) => ( - + ); diff --git a/web/src/components/CasesDisplay/index.tsx b/web/src/components/CasesDisplay/index.tsx index a02a5e4fc..74b3513a4 100644 --- a/web/src/components/CasesDisplay/index.tsx +++ b/web/src/components/CasesDisplay/index.tsx @@ -10,6 +10,7 @@ const StyledHR = styled.hr` `; interface ICasesDisplay extends ICasesGrid { + numberClosedDisputes?: number; title?: string; className?: string; } @@ -19,6 +20,7 @@ const CasesDisplay: React.FC = ({ currentPage, setCurrentPage, numberDisputes, + numberClosedDisputes, casesPerPage, title = "Cases", className, @@ -26,21 +28,17 @@ const CasesDisplay: React.FC = ({

{title}

- + - {disputes.length > 0 ? ( - - ) : ( -

wow no cases

- )} +
); diff --git a/web/src/components/DisputeCard/index.tsx b/web/src/components/DisputeCard/index.tsx index aff25709f..474c0feec 100644 --- a/web/src/components/DisputeCard/index.tsx +++ b/web/src/components/DisputeCard/index.tsx @@ -5,7 +5,7 @@ import { formatEther } from "viem"; import Skeleton from "react-loading-skeleton"; import { Card } from "@kleros/ui-components-library"; import { Periods } from "consts/periods"; -import { CasesPageQuery } from "queries/useCasesQuery"; +import { DisputeDetailsFragment } from "queries/useCasesQuery"; import { useCourtPolicy } from "queries/useCourtPolicy"; import { useDisputeTemplate } from "queries/useDisputeTemplate"; import DisputeInfo from "./DisputeInfo"; @@ -39,13 +39,7 @@ export const getPeriodEndTimestamp = ( return parseInt(lastPeriodChange) + durationCurrentPeriod; }; -const DisputeCard: React.FC = ({ - id, - arbitrated, - period, - lastPeriodChange, - court, -}) => { +const DisputeCard: React.FC = ({ id, arbitrated, period, lastPeriodChange, court }) => { const currentPeriodIndex = Periods[period]; const rewards = `≥ ${formatEther(court.feeForJuror)} ETH`; const date = diff --git a/web/src/hooks/queries/useCasesQuery.ts b/web/src/hooks/queries/useCasesQuery.ts index c6b813924..f0797b834 100644 --- a/web/src/hooks/queries/useCasesQuery.ts +++ b/web/src/hooks/queries/useCasesQuery.ts @@ -1,37 +1,67 @@ import { graphql } from "src/graphql"; +import { Address } from "viem"; import { useQuery } from "@tanstack/react-query"; -import { CasesPageQuery } from "src/graphql/graphql"; -import { graphqlQueryFnHelper } from "~src/utils/graphqlQueryFnHelper"; -export type { CasesPageQuery }; +import { CasesPageQuery, Dispute_Filter, MyCasesQuery, DisputeDetailsFragment } from "src/graphql/graphql"; +import { graphqlQueryFnHelper } from "utils/graphqlQueryFnHelper"; +import { isUndefined } from "utils/index"; +export type { CasesPageQuery, DisputeDetailsFragment }; + +export const disputeFragment = graphql(` + fragment DisputeDetails on Dispute { + id + arbitrated { + id + } + court { + id + policy + feeForJuror + timesPerPeriod + } + period + lastPeriodChange + } +`); + +const casesQueryWhere = graphql(` + query CasesPageWhere($skip: Int, $where: Dispute_filter) { + disputes(first: 3, skip: $skip, orderBy: lastPeriodChange, orderDirection: desc, where: $where) { + ...DisputeDetails + } + } +`); const casesQuery = graphql(` query CasesPage($skip: Int) { disputes(first: 3, skip: $skip, orderBy: lastPeriodChange, orderDirection: desc) { - id - arbitrated { - id - } - court { - id - policy - feeForJuror - timesPerPeriod - } - period - lastPeriodChange + ...DisputeDetails } - counter(id: "0") { - cases + } +`); + +const myCasesQuery = graphql(` + query MyCases($id: ID!, $skip: Int) { + user(id: $id) { + disputes(first: 3, skip: $skip, orderBy: lastPeriodChange, orderDirection: desc) { + ...DisputeDetails + } } } `); -export const useCasesQuery = (skip: number) => { - const isEnabled = skip !== undefined; +export const useCasesQuery = (skip = 0, where?: Dispute_Filter) => { + return useQuery({ + queryKey: [`useCasesQuery`, skip], + queryFn: async () => await graphqlQueryFnHelper(isUndefined(where) ? casesQuery : casesQueryWhere, { skip, where }), + }); +}; + +export const useMyCasesQuery = (user?: Address, skip = 0) => { + const isEnabled = !isUndefined(user); - return useQuery({ - queryKey: [`useCasesQuery${skip}`], + return useQuery({ + queryKey: [`useMyCasesQuery`, user, skip], enabled: isEnabled, - queryFn: async () => await graphqlQueryFnHelper(casesQuery, { skip: skip }), + queryFn: async () => await graphqlQueryFnHelper(myCasesQuery, { skip, id: user?.toLowerCase() }), }); }; diff --git a/web/src/hooks/queries/useCounter.ts b/web/src/hooks/queries/useCounter.ts new file mode 100644 index 000000000..5b7aebc0e --- /dev/null +++ b/web/src/hooks/queries/useCounter.ts @@ -0,0 +1,20 @@ +import { graphql } from "src/graphql"; +import { useQuery } from "@tanstack/react-query"; +import { CounterQuery } from "src/graphql/graphql"; +import { graphqlQueryFnHelper } from "utils/graphqlQueryFnHelper"; + +const counterQuery = graphql(` + query Counter { + counter(id: "0") { + cases + casesRuled + } + } +`); + +export const useCounterQuery = () => { + return useQuery({ + queryKey: [`useCounterQuery`], + queryFn: async () => await graphqlQueryFnHelper(counterQuery, {}), + }); +}; diff --git a/web/src/hooks/queries/useUser.ts b/web/src/hooks/queries/useUser.ts index e457785f7..c49eefab0 100644 --- a/web/src/hooks/queries/useUser.ts +++ b/web/src/hooks/queries/useUser.ts @@ -1,4 +1,5 @@ import { useQuery } from "@tanstack/react-query"; +import { Address } from "viem"; import { graphql } from "src/graphql"; import { UserQuery } from "src/graphql/graphql"; import { graphqlQueryFnHelper } from "utils/graphqlQueryFnHelper"; @@ -24,12 +25,12 @@ const userQuery = graphql(` } `); -export const useUserQuery = (address?: string) => { +export const useUserQuery = (address?: Address) => { const isEnabled = address !== undefined; return useQuery({ - queryKey: [`userQuery${address}`], + queryKey: [`userQuery${address?.toLowerCase()}`], enabled: isEnabled, - queryFn: async () => await graphqlQueryFnHelper(userQuery, { address }), + queryFn: async () => await graphqlQueryFnHelper(userQuery, { address: address?.toLowerCase() }), }); }; diff --git a/web/src/pages/Cases/index.tsx b/web/src/pages/Cases/index.tsx index 86b623fdb..b69c326fc 100644 --- a/web/src/pages/Cases/index.tsx +++ b/web/src/pages/Cases/index.tsx @@ -1,7 +1,8 @@ import React, { useState } from "react"; import styled from "styled-components"; import { Routes, Route } from "react-router-dom"; -import { useCasesQuery } from "queries/useCasesQuery"; +import { DisputeDetailsFragment, useCasesQuery } from "queries/useCasesQuery"; +import { useCounterQuery } from "queries/useCounter"; import CasesDisplay from "components/CasesDisplay"; import CaseDetails from "./CaseDetails"; @@ -16,19 +17,19 @@ const Cases: React.FC = () => { const [currentPage, setCurrentPage] = useState(1); const casesPerPage = 3; const { data } = useCasesQuery(casesPerPage * (currentPage - 1)); + const { data: counterData } = useCounterQuery(); return ( - ) + } /> } /> diff --git a/web/src/pages/Dashboard/index.tsx b/web/src/pages/Dashboard/index.tsx index 9fcf5f29b..e0e379cc4 100644 --- a/web/src/pages/Dashboard/index.tsx +++ b/web/src/pages/Dashboard/index.tsx @@ -1,7 +1,8 @@ import React, { useState } from "react"; import styled from "styled-components"; import { useAccount } from "wagmi"; -import { useCasesQuery } from "queries/useCasesQuery"; +import { DisputeDetailsFragment, useMyCasesQuery } from "queries/useCasesQuery"; +import { useUserQuery } from "queries/useUser"; import JurorInfo from "./JurorInfo"; import Courts from "./Courts"; import CasesDisplay from "components/CasesDisplay"; @@ -27,10 +28,11 @@ const ConnectWalletContainer = styled.div` `; const Dashboard: React.FC = () => { - const { isConnected } = useAccount(); + const { isConnected, address } = useAccount(); const [currentPage, setCurrentPage] = useState(1); const casesPerPage = 3; - const { data } = useCasesQuery(casesPerPage * (currentPage - 1)); + const { data: disputesData } = useMyCasesQuery(address, casesPerPage * (currentPage - 1)); + const { data: userData } = useUserQuery(address); return ( @@ -38,14 +40,13 @@ const Dashboard: React.FC = () => { <> - {data && ( - - )} + ) : ( From 47223ebc01b70cd3e3f95dc5db90a21ebc8cad32 Mon Sep 17 00:00:00 2001 From: alcercu <333aleix333@gmail.com> Date: Fri, 18 Aug 2023 16:25:07 +0200 Subject: [PATCH 03/36] style(web): improve color for light mode skeleton --- web/src/styles/global-style.ts | 2 +- web/src/styles/themes.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/web/src/styles/global-style.ts b/web/src/styles/global-style.ts index a3ff246ea..0c535712c 100644 --- a/web/src/styles/global-style.ts +++ b/web/src/styles/global-style.ts @@ -10,8 +10,8 @@ export const GlobalStyle = createGlobalStyle` .react-loading-skeleton { z-index: 0; - --highlight-color: ${({ theme }) => theme.stroke}; --base-color: ${({ theme }) => theme.lightGrey}; + --highlight-color: ${({ theme }) => theme.stroke}; } body { diff --git a/web/src/styles/themes.ts b/web/src/styles/themes.ts index ec1593f91..9c854a4ed 100644 --- a/web/src/styles/themes.ts +++ b/web/src/styles/themes.ts @@ -13,7 +13,7 @@ export const lightTheme = { primaryText: "#333333", secondaryText: "#999999", stroke: "#e5e5e5", - lightGrey: "#FAFAFA", + lightGrey: "#F0F0F0", whiteBackground: "#FFFFFF", lightBackground: "#FAFBFC", From c04e8c78e63f1140bfb2e0e68d1411118e7d5d3d Mon Sep 17 00:00:00 2001 From: jaybuidl Date: Tue, 22 Aug 2023 15:50:01 +0800 Subject: [PATCH 04/36] fix: empty CasesGrid when user has never staked --- web/src/components/CasesDisplay/CasesGrid.tsx | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/web/src/components/CasesDisplay/CasesGrid.tsx b/web/src/components/CasesDisplay/CasesGrid.tsx index c5d1a593a..fb68dd43f 100644 --- a/web/src/components/CasesDisplay/CasesGrid.tsx +++ b/web/src/components/CasesDisplay/CasesGrid.tsx @@ -34,15 +34,18 @@ export interface ICasesGrid { } const CasesGrid: React.FC = ({ disputes, currentPage, setCurrentPage, numberDisputes, casesPerPage }) => { + const hasNeverStaked = isUndefined(numberDisputes); return ( <> - - {isUndefined(disputes) - ? [...Array(casesPerPage)].map((_, i) => ) - : disputes.map((dispute, i) => { - return ; - })} - + {!hasNeverStaked && ( + + {isUndefined(disputes) + ? [...Array(casesPerPage)].map((_, i) => ) + : disputes.map((dispute, i) => { + return ; + })} + + )} Date: Fri, 25 Aug 2023 17:50:00 +0300 Subject: [PATCH 05/36] feat: implement filtering and search logic --- subgraph/schema.graphql | 1 + subgraph/src/KlerosCore.ts | 8 +- subgraph/src/datapoint.ts | 16 +- web/.env.devnet.public | 2 +- web/src/app.tsx | 23 +-- web/src/components/CasesDisplay/CasesGrid.tsx | 79 ++++++++- web/src/components/CasesDisplay/Filters.tsx | 11 +- web/src/components/CasesDisplay/Search.tsx | 93 ++++++----- web/src/components/CasesDisplay/index.tsx | 40 +++-- web/src/context/FilterProvider.tsx | 153 ++++++++++++++++++ web/src/context/Web3Provider.tsx | 2 +- web/src/hooks/queries/useCasesQuery.ts | 53 ++++-- web/src/hooks/queries/useCounter.ts | 1 + .../hooks/queries/useMyAppealCasesQuery.ts | 31 ++++ web/src/pages/Cases/index.tsx | 14 +- web/src/pages/Dashboard/Courts/index.tsx | 1 + web/src/pages/Dashboard/index.tsx | 12 +- 17 files changed, 440 insertions(+), 100 deletions(-) create mode 100644 web/src/context/FilterProvider.tsx create mode 100644 web/src/hooks/queries/useMyAppealCasesQuery.ts diff --git a/subgraph/schema.graphql b/subgraph/schema.graphql index ee53ed2bc..c86e6360f 100644 --- a/subgraph/schema.graphql +++ b/subgraph/schema.graphql @@ -195,6 +195,7 @@ type Counter @entity { cases: BigInt! casesVoting: BigInt! casesRuled: BigInt! + casesAppealing: BigInt! } type FeeToken @entity { diff --git a/subgraph/src/KlerosCore.ts b/subgraph/src/KlerosCore.ts index e2c30e895..95b6c10c2 100644 --- a/subgraph/src/KlerosCore.ts +++ b/subgraph/src/KlerosCore.ts @@ -19,7 +19,7 @@ import { createCourtFromEvent, getFeeForJuror } from "./entities/Court"; import { createDisputeKitFromEvent, filterSupportedDisputeKits } from "./entities/DisputeKit"; import { createDisputeFromEvent } from "./entities/Dispute"; import { createRoundFromRoundInfo } from "./entities/Round"; -import { updateCases, updateCasesRuled, updateCasesVoting } from "./datapoint"; +import { updateCases, updateCasesAppealing, updateCasesRuled, updateCasesVoting } from "./datapoint"; import { addUserActiveDispute, ensureUser } from "./entities/User"; import { updateJurorDelayedStake, updateJurorStake } from "./entities/JurorTokensPerCourt"; import { createDrawFromEvent } from "./entities/Draw"; @@ -90,8 +90,12 @@ export function handleNewPeriod(event: NewPeriod): void { const newPeriod = getPeriodName(event.params._period); if (dispute.period === "vote") { updateCasesVoting(BigInt.fromI32(-1), event.block.timestamp); + } else if (newPeriod === "evidence") { + updateCasesAppealing(BigInt.fromI32(-1), event.block.timestamp); } else if (newPeriod === "vote") { updateCasesVoting(ONE, event.block.timestamp); + } else if (newPeriod === "appeal") { + updateCasesAppealing(ONE, event.block.timestamp); } else if (newPeriod === "execution") { const contract = KlerosCore.bind(event.address); const currentRulingInfo = contract.currentRuling(disputeID); @@ -99,7 +103,9 @@ export function handleNewPeriod(event: NewPeriod): void { dispute.overridden = currentRulingInfo.getOverridden(); dispute.tied = currentRulingInfo.getTied(); dispute.save(); + updateCasesAppealing(BigInt.fromI32(-1), event.block.timestamp); } + dispute.period = newPeriod; dispute.lastPeriodChange = event.block.timestamp; dispute.save(); diff --git a/subgraph/src/datapoint.ts b/subgraph/src/datapoint.ts index 4d58805fb..2b098c64c 100644 --- a/subgraph/src/datapoint.ts +++ b/subgraph/src/datapoint.ts @@ -6,7 +6,16 @@ export function getDelta(previousValue: BigInt, newValue: BigInt): BigInt { return newValue.minus(previousValue); } -const VARIABLES = ["stakedPNK", "redistributedPNK", "paidETH", "activeJurors", "cases", "casesVoting", "casesRuled"]; +const VARIABLES = [ + "stakedPNK", + "redistributedPNK", + "paidETH", + "activeJurors", + "cases", + "casesVoting", + "casesRuled", + "casesAppealing", +]; function updateDataPoint(delta: BigInt, timestamp: BigInt, variable: string): void { checkFirstDayActivity(); @@ -33,6 +42,7 @@ function checkFirstDayActivity(): void { counter.cases = ZERO; counter.casesVoting = ZERO; counter.casesRuled = ZERO; + counter.casesAppealing = ZERO; counter.save(); } } @@ -72,3 +82,7 @@ export function updateCasesVoting(delta: BigInt, timestamp: BigInt): void { export function updateCasesRuled(delta: BigInt, timestamp: BigInt): void { updateDataPoint(delta, timestamp, "casesRuled"); } + +export function updateCasesAppealing(delta: BigInt, timestamp: BigInt): void { + updateDataPoint(delta, timestamp, "casesAppealing"); +} diff --git a/web/.env.devnet.public b/web/.env.devnet.public index 498b71954..23b19d862 100644 --- a/web/.env.devnet.public +++ b/web/.env.devnet.public @@ -1,4 +1,4 @@ # Do not enter sensitive information here. export REACT_APP_DEPLOYMENT=devnet -export REACT_APP_KLEROS_CORE_SUBGRAPH_DEVNET=https://api.thegraph.com/subgraphs/name/alcercu/kleroscoredev +export REACT_APP_KLEROS_CORE_SUBGRAPH_DEVNET=https://api.thegraph.com/subgraphs/name/nhestrompia/kleros-v2-core-devnet-test export REACT_APP_DISPUTE_TEMPLATE_ARBGOERLI_SUBGRAPH_DEVNET=https://api.thegraph.com/subgraphs/name/alcercu/templateregistrydevnet \ No newline at end of file diff --git a/web/src/app.tsx b/web/src/app.tsx index f6a705ac6..594489272 100644 --- a/web/src/app.tsx +++ b/web/src/app.tsx @@ -6,6 +6,7 @@ import "react-toastify/dist/ReactToastify.css"; import Web3Provider from "context/Web3Provider"; import QueryClientProvider from "context/QueryClientProvider"; import StyledComponentsProvider from "context/StyledComponentsProvider"; +import { FilterProvider } from "context/FilterProvider"; import RefetchOnBlock from "context/RefetchOnBlock"; import Layout from "layout/index"; import Home from "./pages/Home"; @@ -20,16 +21,18 @@ const App: React.FC = () => { - - }> - } /> - } /> - } /> - } /> - } /> - Justice not found here ¯\_( ͡° ͜ʖ ͡°)_/¯} /> - - + + + }> + } /> + } /> + } /> + } /> + } /> + Justice not found here ¯\_( ͡° ͜ʖ ͡°)_/¯} /> + + + diff --git a/web/src/components/CasesDisplay/CasesGrid.tsx b/web/src/components/CasesDisplay/CasesGrid.tsx index fb68dd43f..34030e447 100644 --- a/web/src/components/CasesDisplay/CasesGrid.tsx +++ b/web/src/components/CasesDisplay/CasesGrid.tsx @@ -1,10 +1,16 @@ import React from "react"; import styled from "styled-components"; +import { useAccount } from "wagmi"; import Skeleton from "react-loading-skeleton"; import { StandardPagination } from "@kleros/ui-components-library"; import { isUndefined } from "utils/index"; import { DisputeDetailsFragment } from "queries/useCasesQuery"; +import { useCounterQuery } from "hooks/queries/useCounter"; +import { useUserQuery } from "hooks/queries/useUser"; +import { useFiltersContext } from "context/FilterProvider"; import DisputeCard from "components/DisputeCard"; +import { CounterQuery, Period, UserQuery } from "~src/graphql/graphql"; +import { useMyAppealCasesQuery } from "~src/hooks/queries/useMyAppealCasesQuery"; const Container = styled.div` display: flex; @@ -33,24 +39,85 @@ export interface ICasesGrid { casesPerPage: number; } +const calculatePages = ( + status: number, + data: CounterQuery | UserQuery, + casesPerPage: number, + numberDisputes: number, + myAppeals?: number +) => { + if (data) { + console.log("dataaa", data); + if ("counter" in data) { + switch (status) { + case 1: + return Math.ceil((data?.counter?.cases - data?.counter?.casesRuled) / casesPerPage); + case 2: + return Math.ceil(data?.counter?.casesRuled / casesPerPage); + case 3: + return Math.ceil(data?.counter?.casesAppealing / casesPerPage); + default: + return Math.ceil((numberDisputes ?? 0) / casesPerPage); + } + } else { + const userQueryData = data as UserQuery; + switch (status) { + case 1: + return Math.ceil( + (userQueryData?.user?.totalDisputes - userQueryData?.user?.totalResolvedDisputes) / casesPerPage + ); + case 2: + return Math.ceil(userQueryData?.user?.totalResolvedDisputes / casesPerPage); + case 3: + return Math.ceil(myAppeals! / casesPerPage); + default: + return Math.ceil((userQueryData.user?.totalDisputes ?? 0) / casesPerPage); + } + } + } else { + return 0; + } +}; + const CasesGrid: React.FC = ({ disputes, currentPage, setCurrentPage, numberDisputes, casesPerPage }) => { + const { address } = useAccount(); const hasNeverStaked = isUndefined(numberDisputes); + const { statusFilter, debouncedSearch, filteredCases, isDashboard } = useFiltersContext(); + const { data: userData } = useUserQuery(address); + const { data: userAppealCases } = useMyAppealCasesQuery(address); + const { data: counterData } = useCounterQuery(); + const userAppealCasesNumber = userAppealCases?.user?.disputes.length; + const totalPages = isDashboard + ? calculatePages(statusFilter, userData!, casesPerPage, numberDisputes!, userAppealCasesNumber) + : calculatePages(statusFilter, counterData!, casesPerPage, numberDisputes!); + console.log( + "🚀 ~ file: CasesGrid.tsx:42 ~ counterData:", + statusFilter, + userData!, + casesPerPage, + numberDisputes!, + userAppealCasesNumber + ); + return ( <> {!hasNeverStaked && ( - {isUndefined(disputes) + {isUndefined(disputes) || isUndefined(filteredCases) ? [...Array(casesPerPage)].map((_, i) => ) : disputes.map((dispute, i) => { return ; })} )} - setCurrentPage(page)} - /> + + {debouncedSearch === "" && ( + setCurrentPage(page)} + /> + )} ); }; diff --git a/web/src/components/CasesDisplay/Filters.tsx b/web/src/components/CasesDisplay/Filters.tsx index d74c06574..c37bda28c 100644 --- a/web/src/components/CasesDisplay/Filters.tsx +++ b/web/src/components/CasesDisplay/Filters.tsx @@ -1,6 +1,7 @@ import React from "react"; import styled, { useTheme } from "styled-components"; import { DropdownSelect } from "@kleros/ui-components-library"; +import { useFiltersContext } from "~src/context/FilterProvider"; const Container = styled.div` display: flex; @@ -11,6 +12,12 @@ const Container = styled.div` const Filters: React.FC = () => { const theme = useTheme(); + const { setTimeFilter, setStatusFilter } = useFiltersContext(); + + const handleStatusChange = (value: string | number) => { + setStatusFilter(Number(value)); + }; + return ( { { value: 3, text: "Appeal", dot: theme.tint }, ]} defaultValue={0} - callback={() => {}} + callback={handleStatusChange} /> { { value: 1, text: "Oldest" }, ]} defaultValue={0} - callback={() => {}} + callback={setTimeFilter} /> ); diff --git a/web/src/components/CasesDisplay/Search.tsx b/web/src/components/CasesDisplay/Search.tsx index de4263a65..5bdfae0e0 100644 --- a/web/src/components/CasesDisplay/Search.tsx +++ b/web/src/components/CasesDisplay/Search.tsx @@ -1,12 +1,13 @@ import React from "react"; import styled from "styled-components"; import { Searchbar, DropdownCascader } from "@kleros/ui-components-library"; - +import { useFiltersContext } from "context/FilterProvider"; const Container = styled.div` display: flex; flex-wrap: wrap; gap: 8px; margin-bottom: 5px; + z-index: 0; `; const StyledSearchbar = styled(Searchbar)` @@ -20,48 +21,52 @@ const StyledSearchbar = styled(Searchbar)` } `; -const Search: React.FC = () => ( -
- - - - { - // Called with the item value when select is clicked - }} - items={[ - { - label: "General Court", - value: 0, - children: [ - { - label: "Blockchain", - value: 1, - children: [ - { - label: "Technical", - value: 2, - }, - { - label: "Non-technical", - value: 3, - }, - { - label: "Other", - value: 4, - }, - ], - }, - { - label: "Marketing Services", - value: 5, - }, - ], - }, - ]} - /> -
-); +const Search: React.FC = () => { + const { search, setSearch } = useFiltersContext(); + + return ( +
+ + setSearch(e.target.value)} /> + + { + // Called with the item value when select is clicked + }} + items={[ + { + label: "General Court", + value: 0, + children: [ + { + label: "Blockchain", + value: 1, + children: [ + { + label: "Technical", + value: 2, + }, + { + label: "Non-technical", + value: 3, + }, + { + label: "Other", + value: 4, + }, + ], + }, + { + label: "Marketing Services", + value: 5, + }, + ], + }, + ]} + /> +
+ ); +}; export default Search; diff --git a/web/src/components/CasesDisplay/index.tsx b/web/src/components/CasesDisplay/index.tsx index 74b3513a4..c6a2e2127 100644 --- a/web/src/components/CasesDisplay/index.tsx +++ b/web/src/components/CasesDisplay/index.tsx @@ -3,6 +3,7 @@ import styled from "styled-components"; import Search from "./Search"; import StatsAndFilters from "./StatsAndFilters"; import CasesGrid, { ICasesGrid } from "./CasesGrid"; +import { useFiltersContext } from "~src/context/FilterProvider"; const StyledHR = styled.hr` margin-top: 24px; @@ -24,22 +25,27 @@ const CasesDisplay: React.FC = ({ casesPerPage, title = "Cases", className, -}) => ( -
-

{title}

- - - - -
-); +}) => { + const { filteredCases } = useFiltersContext(); + + return ( +
+

{title}

+ + + + + 0 ? filteredCases : disputes} + {...{ + currentPage, + setCurrentPage, + numberDisputes, + casesPerPage, + }} + /> +
+ ); +}; export default CasesDisplay; diff --git a/web/src/context/FilterProvider.tsx b/web/src/context/FilterProvider.tsx new file mode 100644 index 000000000..dc6395a12 --- /dev/null +++ b/web/src/context/FilterProvider.tsx @@ -0,0 +1,153 @@ +import React, { useState, createContext, useContext, useMemo, useEffect } from "react"; +import { useDebounce } from "react-use"; +import { useAccount } from "wagmi"; +import { DisputeDetailsFragment, useMyCasesQuery } from "queries/useCasesQuery"; +import { useCasesQuery } from "hooks/queries/useCasesQuery"; +import { OrderDirection, Period } from "~src/graphql/graphql"; + +interface IFilters { + search: string; + setSearch: (arg0: string) => void; + debouncedSearch: string; + statusFilter: number; + courtFilter: number; + filteredCases: DisputeDetailsFragment[]; + setFilteredCases: (arg0: DisputeDetailsFragment[]) => void; + setCourtFilter: (arg0: number) => void; + timeFilter: number | string; + setTimeFilter: (arg0: number | string) => void; + currentPage: number; + setCurrentPage: (arg0: number) => void; + isDashboard: boolean; + setIsDashboard: (arg0: boolean) => void; + setStatusFilter: (arg0: number) => void; +} + +const Context = createContext({ + search: "", + setSearch: () => { + // + }, + debouncedSearch: "", + statusFilter: 0, + setStatusFilter: () => { + // + }, + isDashboard: false, + setIsDashboard: () => { + // + }, + courtFilter: 0, + setCourtFilter: () => { + // + }, + timeFilter: 0, + setTimeFilter: () => { + // + }, + filteredCases: [], + setFilteredCases: () => { + // + }, + currentPage: 1, + setCurrentPage: () => { + // + }, +}); + +const applyFilters = (disputes: DisputeDetailsFragment[], search: string, status: number): DisputeDetailsFragment[] => { + const filteredDisputes = disputes?.filter((dispute) => { + const matchesSearch = !search || dispute.id.includes(search); + const matchesStatus = Period[status] === dispute.period || true; + return matchesSearch && matchesStatus; + }); + + return filteredDisputes; +}; + +const getStatusPeriod = (statusFilter: number) => { + switch (statusFilter) { + case 1: + return { ruled: false }; + case 2: + return { ruled: true }; + case 3: + return { period: Period.Appeal, ruled: false }; + default: + return {}; + } +}; + +export const FilterProvider: React.FC<{ children?: React.ReactNode }> = ({ children }) => { + const { address } = useAccount(); + const [currentPage, setCurrentPage] = useState(1); + const [statusFilter, setStatusFilter] = useState(0); + const [courtFilter, setCourtFilter] = useState(0); + const [isDashboard, setIsDashboard] = useState(false); + const [timeFilter, setTimeFilter] = useState(0); + const [search, setSearch] = useState(""); + const [filteredCases, setFilteredCases] = useState([]); + const [debouncedSearch, setDebouncedSearch] = useState(""); + useDebounce(() => setDebouncedSearch(search), 500, [search]); + const direction = timeFilter === 0 ? OrderDirection.Desc : OrderDirection.Asc; + const disputeSkip = debouncedSearch ? 0 : 3 * (currentPage - 1); + const periodStatus = getStatusPeriod(statusFilter); + const queryFilter = debouncedSearch ? { id: debouncedSearch } : undefined; + + const combinedQueryFilters = { + ...queryFilter, + ...periodStatus, + }; + const { data } = useCasesQuery(disputeSkip, combinedQueryFilters, direction); + const { data: dashboardData } = useMyCasesQuery(address, disputeSkip, combinedQueryFilters, direction); + + const disputes = isDashboard + ? (dashboardData?.user?.disputes as DisputeDetailsFragment[]) + : (data?.disputes as DisputeDetailsFragment[]); + + useEffect(() => { + setCurrentPage(1); + }, [statusFilter]); + + // useEffect(() => { + // const filteredDisputes = disputes?.filter((dispute) => { + // const matchesSearch = debouncedSearch ? dispute.id.includes(debouncedSearch) : true; + // const matchesStatus = dispute.period === "execution" ? true : false; + + // return (matchesSearch && matchesStatus) || []; + // }); + // const disputeData = timeFilter === 1 ? filteredDisputes.reverse() : filteredDisputes; + // console.log("🚀 ~ file: FilterProvider.tsx:69 ~ useEffect ~ disputeData:", disputeData); + + // setFilteredCases(disputeData); + // }, [debouncedSearch, data, timeFilter, courtFilter, statusFilter]); + + useEffect(() => { + const filteredDisputes = applyFilters(disputes, debouncedSearch, statusFilter); + setFilteredCases(filteredDisputes); + }, [debouncedSearch, data, timeFilter, courtFilter, statusFilter]); + + const value = useMemo( + () => ({ + search, + setSearch, + debouncedSearch, + courtFilter, + setCourtFilter, + timeFilter, + setTimeFilter, + statusFilter, + setStatusFilter, + filteredCases, + setFilteredCases, + currentPage, + setCurrentPage, + isDashboard, + setIsDashboard, + }), + [search, debouncedSearch, timeFilter, statusFilter, filteredCases] + ); + return {children}; +}; + +export const useFiltersContext = () => useContext(Context); diff --git a/web/src/context/Web3Provider.tsx b/web/src/context/Web3Provider.tsx index 1677ebbac..c6e161578 100644 --- a/web/src/context/Web3Provider.tsx +++ b/web/src/context/Web3Provider.tsx @@ -8,7 +8,7 @@ import { jsonRpcProvider } from "wagmi/providers/jsonRpc"; import { useToggleTheme } from "hooks/useToggleThemeContext"; import { useTheme } from "styled-components"; -const chains = [mainnet, arbitrumGoerli, gnosisChiado]; +const chains = [arbitrumGoerli, gnosisChiado]; const projectId = process.env.WALLETCONNECT_PROJECT_ID ?? "6efaa26765fa742153baf9281e218217"; const { publicClient, webSocketPublicClient } = configureChains(chains, [ diff --git a/web/src/hooks/queries/useCasesQuery.ts b/web/src/hooks/queries/useCasesQuery.ts index f0797b834..c5e568042 100644 --- a/web/src/hooks/queries/useCasesQuery.ts +++ b/web/src/hooks/queries/useCasesQuery.ts @@ -1,7 +1,13 @@ import { graphql } from "src/graphql"; import { Address } from "viem"; import { useQuery } from "@tanstack/react-query"; -import { CasesPageQuery, Dispute_Filter, MyCasesQuery, DisputeDetailsFragment } from "src/graphql/graphql"; +import { + CasesPageQuery, + Dispute_Filter, + OrderDirection, + MyCasesQuery, + DisputeDetailsFragment, +} from "src/graphql/graphql"; import { graphqlQueryFnHelper } from "utils/graphqlQueryFnHelper"; import { isUndefined } from "utils/index"; export type { CasesPageQuery, DisputeDetailsFragment }; @@ -24,44 +30,65 @@ export const disputeFragment = graphql(` `); const casesQueryWhere = graphql(` - query CasesPageWhere($skip: Int, $where: Dispute_filter) { - disputes(first: 3, skip: $skip, orderBy: lastPeriodChange, orderDirection: desc, where: $where) { + query CasesPageWhere($skip: Int, $where: Dispute_filter, $orderDirection: OrderDirection) { + disputes(first: 3, skip: $skip, orderBy: lastPeriodChange, orderDirection: $orderDirection, where: $where) { ...DisputeDetails } } `); const casesQuery = graphql(` - query CasesPage($skip: Int) { - disputes(first: 3, skip: $skip, orderBy: lastPeriodChange, orderDirection: desc) { + query CasesPage($skip: Int, $orderDirection: OrderDirection) { + disputes(first: 3, skip: $skip, orderBy: lastPeriodChange, orderDirection: $orderDirection) { ...DisputeDetails } } `); const myCasesQuery = graphql(` - query MyCases($id: ID!, $skip: Int) { + query MyCases($id: ID!, $skip: Int, $orderDirection: OrderDirection) { user(id: $id) { - disputes(first: 3, skip: $skip, orderBy: lastPeriodChange, orderDirection: desc) { + disputes(first: 3, skip: $skip, orderBy: lastPeriodChange, orderDirection: $orderDirection) { ...DisputeDetails } } } `); -export const useCasesQuery = (skip = 0, where?: Dispute_Filter) => { +const myCasesQueryWhere = graphql(` + query myCasesPageWhere($id: ID!, $skip: Int, $where: Dispute_filter, $orderDirection: OrderDirection) { + user(id: $id) { + disputes(first: 3, skip: $skip, orderBy: lastPeriodChange, orderDirection: $orderDirection, where: $where) { + ...DisputeDetails + } + } + } +`); + +export const useCasesQuery = (skip = 0, where?: Dispute_Filter, sortOrder?: OrderDirection) => { return useQuery({ - queryKey: [`useCasesQuery`, skip], - queryFn: async () => await graphqlQueryFnHelper(isUndefined(where) ? casesQuery : casesQueryWhere, { skip, where }), + queryKey: [`useCasesQuery`, skip, where, sortOrder], + queryFn: async () => + await graphqlQueryFnHelper(isUndefined(where) ? casesQuery : casesQueryWhere, { + skip, + where, + orderDirection: sortOrder ?? "desc", + }), }); }; -export const useMyCasesQuery = (user?: Address, skip = 0) => { +export const useMyCasesQuery = (user?: Address, skip = 0, where?: Dispute_Filter, sortOrder?: OrderDirection) => { const isEnabled = !isUndefined(user); return useQuery({ - queryKey: [`useMyCasesQuery`, user, skip], + queryKey: [`useMyCasesQuery`, user, skip, where, sortOrder], enabled: isEnabled, - queryFn: async () => await graphqlQueryFnHelper(myCasesQuery, { skip, id: user?.toLowerCase() }), + queryFn: async () => + await graphqlQueryFnHelper(isUndefined(where) ? myCasesQuery : myCasesQueryWhere, { + skip, + id: user?.toLowerCase(), + where, + orderDirection: sortOrder ?? "desc", + }), }); }; diff --git a/web/src/hooks/queries/useCounter.ts b/web/src/hooks/queries/useCounter.ts index 5b7aebc0e..131f9774e 100644 --- a/web/src/hooks/queries/useCounter.ts +++ b/web/src/hooks/queries/useCounter.ts @@ -8,6 +8,7 @@ const counterQuery = graphql(` counter(id: "0") { cases casesRuled + casesAppealing } } `); diff --git a/web/src/hooks/queries/useMyAppealCasesQuery.ts b/web/src/hooks/queries/useMyAppealCasesQuery.ts new file mode 100644 index 000000000..3e9d5835a --- /dev/null +++ b/web/src/hooks/queries/useMyAppealCasesQuery.ts @@ -0,0 +1,31 @@ +import { graphql } from "src/graphql"; +import { Address } from "viem"; +import { useQuery } from "@tanstack/react-query"; +import { MyAppealCasesQuery, Period, Dispute_Filter } from "src/graphql/graphql"; +import { graphqlQueryFnHelper } from "utils/graphqlQueryFnHelper"; +import { isUndefined } from "utils/index"; +export type { MyAppealCasesQuery }; + +const myAppealCases = graphql(` + query MyAppealCases($id: ID!, $where: Dispute_filter) { + user(id: $id) { + disputes(orderBy: lastPeriodChange, where: $where) { + period + } + } + } +`); + +export const useMyAppealCasesQuery = (user?: Address) => { + const isEnabled = !isUndefined(user); + + return useQuery({ + queryKey: [`useMyAppealCasesQuery`, user], + enabled: isEnabled, + queryFn: async () => + await graphqlQueryFnHelper(myAppealCases, { + id: user?.toLowerCase(), + where: { period: Period.Appeal }, + }), + }); +}; diff --git a/web/src/pages/Cases/index.tsx b/web/src/pages/Cases/index.tsx index b69c326fc..5f13e2819 100644 --- a/web/src/pages/Cases/index.tsx +++ b/web/src/pages/Cases/index.tsx @@ -1,6 +1,8 @@ -import React, { useState } from "react"; +import React, { useEffect } from "react"; import styled from "styled-components"; import { Routes, Route } from "react-router-dom"; +import { useAccount } from "wagmi"; +import { useFiltersContext } from "context/FilterProvider"; import { DisputeDetailsFragment, useCasesQuery } from "queries/useCasesQuery"; import { useCounterQuery } from "queries/useCounter"; import CasesDisplay from "components/CasesDisplay"; @@ -14,10 +16,18 @@ const Container = styled.div` `; const Cases: React.FC = () => { - const [currentPage, setCurrentPage] = useState(1); + const { isConnected } = useAccount(); const casesPerPage = 3; + const { setCurrentPage, currentPage } = useFiltersContext(); const { data } = useCasesQuery(casesPerPage * (currentPage - 1)); const { data: counterData } = useCounterQuery(); + + const { setIsDashboard } = useFiltersContext(); + + useEffect(() => { + setIsDashboard(false); + }, [isConnected]); + return ( diff --git a/web/src/pages/Dashboard/Courts/index.tsx b/web/src/pages/Dashboard/Courts/index.tsx index 923e7969f..f6be75422 100644 --- a/web/src/pages/Dashboard/Courts/index.tsx +++ b/web/src/pages/Dashboard/Courts/index.tsx @@ -13,6 +13,7 @@ const CourtsContainer = styled.div` display: flex; flex-direction: column; gap: 8px; + z-index: 0; `; const Courts: React.FC = () => { diff --git a/web/src/pages/Dashboard/index.tsx b/web/src/pages/Dashboard/index.tsx index e0e379cc4..419984750 100644 --- a/web/src/pages/Dashboard/index.tsx +++ b/web/src/pages/Dashboard/index.tsx @@ -1,12 +1,14 @@ -import React, { useState } from "react"; +import React, { useEffect } from "react"; import styled from "styled-components"; import { useAccount } from "wagmi"; import { DisputeDetailsFragment, useMyCasesQuery } from "queries/useCasesQuery"; +import { useFiltersContext } from "context/FilterProvider"; import { useUserQuery } from "queries/useUser"; import JurorInfo from "./JurorInfo"; import Courts from "./Courts"; import CasesDisplay from "components/CasesDisplay"; import ConnectWallet from "components/ConnectWallet"; +import { useMyAppealCasesQuery } from "~src/hooks/queries/useMyAppealCasesQuery"; const Container = styled.div` width: 100%; @@ -29,10 +31,16 @@ const ConnectWalletContainer = styled.div` const Dashboard: React.FC = () => { const { isConnected, address } = useAccount(); - const [currentPage, setCurrentPage] = useState(1); + const { currentPage, setCurrentPage } = useFiltersContext(); const casesPerPage = 3; const { data: disputesData } = useMyCasesQuery(address, casesPerPage * (currentPage - 1)); const { data: userData } = useUserQuery(address); + const { setIsDashboard } = useFiltersContext(); + const { data: appealingCases } = useMyAppealCasesQuery(address); + + useEffect(() => { + setIsDashboard(true); + }, [isConnected]); return ( From 91351b6352321fda0b9b67e38852aa82054791b4 Mon Sep 17 00:00:00 2001 From: marino <102478601+kemuru@users.noreply.github.com> Date: Mon, 28 Aug 2023 13:23:41 +0200 Subject: [PATCH 06/36] fix(web): dashboard dont show my courts if no staked anymore --- web/src/pages/Dashboard/Courts/CourtCard.tsx | 28 +++++++++++--------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/web/src/pages/Dashboard/Courts/CourtCard.tsx b/web/src/pages/Dashboard/Courts/CourtCard.tsx index b7d385d57..394151d43 100644 --- a/web/src/pages/Dashboard/Courts/CourtCard.tsx +++ b/web/src/pages/Dashboard/Courts/CourtCard.tsx @@ -49,19 +49,21 @@ const CourtCard: React.FC = ({ id, name }) => { }); return ( - - - - - {`${format(jurorBalance?.[0])} PNK`} - - - - - - {`${format(jurorBalance?.[1])} PNK`} - - + (format(jurorBalance?.[0]) || format(jurorBalance?.[1])) !== "0" && ( + + + + + {`${format(jurorBalance?.[0])} PNK`} + + + + + + {`${format(jurorBalance?.[1])} PNK`} + + + ) ); }; From 1bdd14aa14ee3afd3246ef1f8bb7afdb657f6d53 Mon Sep 17 00:00:00 2001 From: marino <102478601+kemuru@users.noreply.github.com> Date: Mon, 28 Aug 2023 13:30:11 +0200 Subject: [PATCH 07/36] chore(web): abstract variables --- web/src/pages/Dashboard/Courts/CourtCard.tsx | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/web/src/pages/Dashboard/Courts/CourtCard.tsx b/web/src/pages/Dashboard/Courts/CourtCard.tsx index 394151d43..2c1fbd147 100644 --- a/web/src/pages/Dashboard/Courts/CourtCard.tsx +++ b/web/src/pages/Dashboard/Courts/CourtCard.tsx @@ -48,19 +48,22 @@ const CourtCard: React.FC = ({ id, name }) => { watch: true, }); + const stake = format(jurorBalance?.[0]); + const lockedStake = format(jurorBalance?.[1]); + return ( - (format(jurorBalance?.[0]) || format(jurorBalance?.[1])) !== "0" && ( + (stake || lockedStake) !== "0" && ( - {`${format(jurorBalance?.[0])} PNK`} + {`${stake} PNK`} - {`${format(jurorBalance?.[1])} PNK`} + {`${lockedStake} PNK`} ) From 4bed1b36c52ffa99f5907f78342ac06d08006d09 Mon Sep 17 00:00:00 2001 From: nhestrompia Date: Mon, 28 Aug 2023 14:29:17 +0300 Subject: [PATCH 08/36] refactor: clear naming for condition --- web/src/components/CasesDisplay/CasesGrid.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/web/src/components/CasesDisplay/CasesGrid.tsx b/web/src/components/CasesDisplay/CasesGrid.tsx index 34030e447..825784bd8 100644 --- a/web/src/components/CasesDisplay/CasesGrid.tsx +++ b/web/src/components/CasesDisplay/CasesGrid.tsx @@ -81,7 +81,6 @@ const calculatePages = ( const CasesGrid: React.FC = ({ disputes, currentPage, setCurrentPage, numberDisputes, casesPerPage }) => { const { address } = useAccount(); - const hasNeverStaked = isUndefined(numberDisputes); const { statusFilter, debouncedSearch, filteredCases, isDashboard } = useFiltersContext(); const { data: userData } = useUserQuery(address); const { data: userAppealCases } = useMyAppealCasesQuery(address); @@ -101,7 +100,7 @@ const CasesGrid: React.FC = ({ disputes, currentPage, setCurrentPage return ( <> - {!hasNeverStaked && ( + {!isUndefined(numberDisputes) && ( {isUndefined(disputes) || isUndefined(filteredCases) ? [...Array(casesPerPage)].map((_, i) => ) From a69b33566dfb2b8cd95582f25b46651a7ab20577 Mon Sep 17 00:00:00 2001 From: nhestrompia Date: Mon, 11 Sep 2023 04:52:10 +0300 Subject: [PATCH 09/36] feat: add court selection filtering and subgraph update --- subgraph/schema.graphql | 2 + subgraph/src/KlerosCore.ts | 20 +++++- subgraph/src/entities/Court.ts | 2 + web/src/components/CasesDisplay/CasesGrid.tsx | 66 ++++++++++--------- web/src/components/CasesDisplay/Search.tsx | 6 +- web/src/components/CasesDisplay/index.tsx | 26 ++++---- web/src/context/FilterProvider.tsx | 39 ++++------- web/src/hooks/queries/useCourtDetails.ts | 2 + .../hooks/queries/useMyAppealCasesQuery.ts | 2 +- web/src/pages/Courts/TopSearch.tsx | 16 +---- web/src/pages/Dashboard/index.tsx | 2 - web/src/utils/graphqlQueryFnHelper.ts | 2 +- 12 files changed, 95 insertions(+), 90 deletions(-) diff --git a/subgraph/schema.graphql b/subgraph/schema.graphql index c86e6360f..d1aafe19b 100644 --- a/subgraph/schema.graphql +++ b/subgraph/schema.graphql @@ -128,6 +128,8 @@ type Court @entity { supportedDisputeKits: [DisputeKit!]! disputes: [Dispute!]! @derivedFrom(field: "court") numberDisputes: BigInt! + numberClosedDisputes: BigInt! + numberAppealingDisputes: BigInt! stakedJurors: [JurorTokensPerCourt!]! @derivedFrom(field: "court") numberStakedJurors: BigInt! stake: BigInt! diff --git a/subgraph/src/KlerosCore.ts b/subgraph/src/KlerosCore.ts index 95b6c10c2..03f10cbd7 100644 --- a/subgraph/src/KlerosCore.ts +++ b/subgraph/src/KlerosCore.ts @@ -84,17 +84,24 @@ export function handleDisputeCreation(event: DisputeCreation): void { } export function handleNewPeriod(event: NewPeriod): void { + const contract = KlerosCore.bind(event.address); const disputeID = event.params._disputeID; const dispute = Dispute.load(disputeID.toString()); + const disputeStorage = contract.disputes(disputeID); + const courtID = disputeStorage.value0.toString(); + const court = Court.load(courtID); if (!dispute) return; + if (!court) return; const newPeriod = getPeriodName(event.params._period); if (dispute.period === "vote") { updateCasesVoting(BigInt.fromI32(-1), event.block.timestamp); } else if (newPeriod === "evidence") { + court.numberAppealingDisputes = court.numberAppealingDisputes.minus(ONE); updateCasesAppealing(BigInt.fromI32(-1), event.block.timestamp); } else if (newPeriod === "vote") { updateCasesVoting(ONE, event.block.timestamp); } else if (newPeriod === "appeal") { + court.numberAppealingDisputes = court.numberAppealingDisputes.plus(ONE); updateCasesAppealing(ONE, event.block.timestamp); } else if (newPeriod === "execution") { const contract = KlerosCore.bind(event.address); @@ -103,21 +110,30 @@ export function handleNewPeriod(event: NewPeriod): void { dispute.overridden = currentRulingInfo.getOverridden(); dispute.tied = currentRulingInfo.getTied(); dispute.save(); + court.numberAppealingDisputes = court.numberAppealingDisputes.minus(ONE); updateCasesAppealing(BigInt.fromI32(-1), event.block.timestamp); } dispute.period = newPeriod; dispute.lastPeriodChange = event.block.timestamp; dispute.save(); + court.save(); } export function handleRuling(event: Ruling): void { - const disputeID = event.params._disputeID.toString(); - const dispute = Dispute.load(disputeID); + const contract = KlerosCore.bind(event.address); + const disputeID = event.params._disputeID; + const dispute = Dispute.load(disputeID.toString()); + const disputeStorage = contract.disputes(disputeID); + const courtID = disputeStorage.value0.toString(); + const court = Court.load(courtID); if (!dispute) return; dispute.ruled = true; dispute.save(); updateCasesRuled(ONE, event.block.timestamp); + if (!court) return; + court.numberClosedDisputes = court.numberClosedDisputes.plus(ONE); + court.save(); } export function handleAppealDecision(event: AppealDecision): void { diff --git a/subgraph/src/entities/Court.ts b/subgraph/src/entities/Court.ts index 520f9b2c7..895457846 100644 --- a/subgraph/src/entities/Court.ts +++ b/subgraph/src/entities/Court.ts @@ -14,6 +14,8 @@ export function createCourtFromEvent(event: CourtCreated): void { court.timesPerPeriod = event.params._timesPerPeriod; court.supportedDisputeKits = event.params._supportedDisputeKits.map((value) => value.toString()); court.numberDisputes = ZERO; + court.numberClosedDisputes = ZERO; + court.numberAppealingDisputes = ZERO; court.numberStakedJurors = ZERO; court.stake = ZERO; court.delayedStake = ZERO; diff --git a/web/src/components/CasesDisplay/CasesGrid.tsx b/web/src/components/CasesDisplay/CasesGrid.tsx index 825784bd8..47ecdca7c 100644 --- a/web/src/components/CasesDisplay/CasesGrid.tsx +++ b/web/src/components/CasesDisplay/CasesGrid.tsx @@ -9,7 +9,7 @@ import { useCounterQuery } from "hooks/queries/useCounter"; import { useUserQuery } from "hooks/queries/useUser"; import { useFiltersContext } from "context/FilterProvider"; import DisputeCard from "components/DisputeCard"; -import { CounterQuery, Period, UserQuery } from "~src/graphql/graphql"; +import { CounterQuery, UserQuery } from "src/graphql/graphql"; import { useMyAppealCasesQuery } from "~src/hooks/queries/useMyAppealCasesQuery"; const Container = styled.div` @@ -41,42 +41,56 @@ export interface ICasesGrid { const calculatePages = ( status: number, - data: CounterQuery | UserQuery, + data: CounterQuery | UserQuery | undefined, casesPerPage: number, numberDisputes: number, myAppeals?: number ) => { - if (data) { - console.log("dataaa", data); - if ("counter" in data) { + if (!data) { + return 0; + } + + let totalPages = 0; + + if ("counter" in data) { + const counterQueryData = data as CounterQuery; + const counter = counterQueryData.counter; + if (counter) { switch (status) { case 1: - return Math.ceil((data?.counter?.cases - data?.counter?.casesRuled) / casesPerPage); + totalPages = counter.cases - counter.casesRuled; + break; case 2: - return Math.ceil(data?.counter?.casesRuled / casesPerPage); + totalPages = counter.casesRuled; + break; case 3: - return Math.ceil(data?.counter?.casesAppealing / casesPerPage); + totalPages = counter.casesAppealing; + break; default: - return Math.ceil((numberDisputes ?? 0) / casesPerPage); + totalPages = numberDisputes ?? 0; } - } else { - const userQueryData = data as UserQuery; + } + } else { + const userQueryData = data as UserQuery; + const user = userQueryData.user; + if (user) { switch (status) { case 1: - return Math.ceil( - (userQueryData?.user?.totalDisputes - userQueryData?.user?.totalResolvedDisputes) / casesPerPage - ); + totalPages = (user.totalDisputes ?? 0) - (user.totalResolvedDisputes ?? 0); + break; case 2: - return Math.ceil(userQueryData?.user?.totalResolvedDisputes / casesPerPage); + totalPages = user.totalResolvedDisputes ?? 0; + break; case 3: - return Math.ceil(myAppeals! / casesPerPage); + totalPages = myAppeals ?? 0; + break; default: - return Math.ceil((userQueryData.user?.totalDisputes ?? 0) / casesPerPage); + totalPages = user.totalDisputes ?? 0; } } - } else { - return 0; } + + return totalPages / casesPerPage; }; const CasesGrid: React.FC = ({ disputes, currentPage, setCurrentPage, numberDisputes, casesPerPage }) => { @@ -87,16 +101,8 @@ const CasesGrid: React.FC = ({ disputes, currentPage, setCurrentPage const { data: counterData } = useCounterQuery(); const userAppealCasesNumber = userAppealCases?.user?.disputes.length; const totalPages = isDashboard - ? calculatePages(statusFilter, userData!, casesPerPage, numberDisputes!, userAppealCasesNumber) - : calculatePages(statusFilter, counterData!, casesPerPage, numberDisputes!); - console.log( - "🚀 ~ file: CasesGrid.tsx:42 ~ counterData:", - statusFilter, - userData!, - casesPerPage, - numberDisputes!, - userAppealCasesNumber - ); + ? calculatePages(statusFilter, userData, casesPerPage, numberDisputes ?? 0, userAppealCasesNumber) + : calculatePages(statusFilter, counterData, casesPerPage, numberDisputes ?? 0); return ( <> @@ -113,7 +119,7 @@ const CasesGrid: React.FC = ({ disputes, currentPage, setCurrentPage {debouncedSearch === "" && ( setCurrentPage(page)} /> )} diff --git a/web/src/components/CasesDisplay/Search.tsx b/web/src/components/CasesDisplay/Search.tsx index 5bdfae0e0..6d059e3a8 100644 --- a/web/src/components/CasesDisplay/Search.tsx +++ b/web/src/components/CasesDisplay/Search.tsx @@ -22,7 +22,7 @@ const StyledSearchbar = styled(Searchbar)` `; const Search: React.FC = () => { - const { search, setSearch } = useFiltersContext(); + const { search, setSearch, setCourtFilter } = useFiltersContext(); return (
@@ -31,9 +31,7 @@ const Search: React.FC = () => { { - // Called with the item value when select is clicked - }} + onSelect={setCourtFilter} items={[ { label: "General Court", diff --git a/web/src/components/CasesDisplay/index.tsx b/web/src/components/CasesDisplay/index.tsx index c6a2e2127..468268cf6 100644 --- a/web/src/components/CasesDisplay/index.tsx +++ b/web/src/components/CasesDisplay/index.tsx @@ -3,7 +3,7 @@ import styled from "styled-components"; import Search from "./Search"; import StatsAndFilters from "./StatsAndFilters"; import CasesGrid, { ICasesGrid } from "./CasesGrid"; -import { useFiltersContext } from "~src/context/FilterProvider"; +import { useFiltersContext } from "context/FilterProvider"; const StyledHR = styled.hr` margin-top: 24px; @@ -26,7 +26,7 @@ const CasesDisplay: React.FC = ({ title = "Cases", className, }) => { - const { filteredCases } = useFiltersContext(); + const { filteredCases, isFilterApplied } = useFiltersContext(); return (
@@ -35,15 +35,19 @@ const CasesDisplay: React.FC = ({ - 0 ? filteredCases : disputes} - {...{ - currentPage, - setCurrentPage, - numberDisputes, - casesPerPage, - }} - /> + {isFilterApplied && filteredCases?.length === 0 ? ( +

No cases found

+ ) : ( + 0 ? filteredCases : disputes} + {...{ + currentPage, + setCurrentPage, + numberDisputes, + casesPerPage, + }} + /> + )}
); }; diff --git a/web/src/context/FilterProvider.tsx b/web/src/context/FilterProvider.tsx index dc6395a12..86390fc0a 100644 --- a/web/src/context/FilterProvider.tsx +++ b/web/src/context/FilterProvider.tsx @@ -3,7 +3,7 @@ import { useDebounce } from "react-use"; import { useAccount } from "wagmi"; import { DisputeDetailsFragment, useMyCasesQuery } from "queries/useCasesQuery"; import { useCasesQuery } from "hooks/queries/useCasesQuery"; -import { OrderDirection, Period } from "~src/graphql/graphql"; +import { OrderDirection, Period } from "src/graphql/graphql"; interface IFilters { search: string; @@ -21,6 +21,7 @@ interface IFilters { isDashboard: boolean; setIsDashboard: (arg0: boolean) => void; setStatusFilter: (arg0: number) => void; + isFilterApplied: boolean; } const Context = createContext({ @@ -53,18 +54,9 @@ const Context = createContext({ setCurrentPage: () => { // }, + isFilterApplied: false, }); -const applyFilters = (disputes: DisputeDetailsFragment[], search: string, status: number): DisputeDetailsFragment[] => { - const filteredDisputes = disputes?.filter((dispute) => { - const matchesSearch = !search || dispute.id.includes(search); - const matchesStatus = Period[status] === dispute.period || true; - return matchesSearch && matchesStatus; - }); - - return filteredDisputes; -}; - const getStatusPeriod = (statusFilter: number) => { switch (statusFilter) { case 1: @@ -80,6 +72,7 @@ const getStatusPeriod = (statusFilter: number) => { export const FilterProvider: React.FC<{ children?: React.ReactNode }> = ({ children }) => { const { address } = useAccount(); + const [isFilterApplied, setIsFilterApplied] = useState(false); const [currentPage, setCurrentPage] = useState(1); const [statusFilter, setStatusFilter] = useState(0); const [courtFilter, setCourtFilter] = useState(0); @@ -93,10 +86,12 @@ export const FilterProvider: React.FC<{ children?: React.ReactNode }> = ({ child const disputeSkip = debouncedSearch ? 0 : 3 * (currentPage - 1); const periodStatus = getStatusPeriod(statusFilter); const queryFilter = debouncedSearch ? { id: debouncedSearch } : undefined; + const courtChoice = courtFilter === 0 ? {} : { court: courtFilter.toString() }; const combinedQueryFilters = { ...queryFilter, ...periodStatus, + ...courtChoice, }; const { data } = useCasesQuery(disputeSkip, combinedQueryFilters, direction); const { data: dashboardData } = useMyCasesQuery(address, disputeSkip, combinedQueryFilters, direction); @@ -109,22 +104,13 @@ export const FilterProvider: React.FC<{ children?: React.ReactNode }> = ({ child setCurrentPage(1); }, [statusFilter]); - // useEffect(() => { - // const filteredDisputes = disputes?.filter((dispute) => { - // const matchesSearch = debouncedSearch ? dispute.id.includes(debouncedSearch) : true; - // const matchesStatus = dispute.period === "execution" ? true : false; - - // return (matchesSearch && matchesStatus) || []; - // }); - // const disputeData = timeFilter === 1 ? filteredDisputes.reverse() : filteredDisputes; - // console.log("🚀 ~ file: FilterProvider.tsx:69 ~ useEffect ~ disputeData:", disputeData); - - // setFilteredCases(disputeData); - // }, [debouncedSearch, data, timeFilter, courtFilter, statusFilter]); - useEffect(() => { - const filteredDisputes = applyFilters(disputes, debouncedSearch, statusFilter); - setFilteredCases(filteredDisputes); + if (search !== "" || statusFilter !== 0 || courtFilter !== 0 || timeFilter !== 0) { + setIsFilterApplied(true); + } else { + setIsFilterApplied(false); + } + setFilteredCases(disputes); }, [debouncedSearch, data, timeFilter, courtFilter, statusFilter]); const value = useMemo( @@ -144,6 +130,7 @@ export const FilterProvider: React.FC<{ children?: React.ReactNode }> = ({ child setCurrentPage, isDashboard, setIsDashboard, + isFilterApplied, }), [search, debouncedSearch, timeFilter, statusFilter, filteredCases] ); diff --git a/web/src/hooks/queries/useCourtDetails.ts b/web/src/hooks/queries/useCourtDetails.ts index ad6f1c966..90a820842 100644 --- a/web/src/hooks/queries/useCourtDetails.ts +++ b/web/src/hooks/queries/useCourtDetails.ts @@ -11,6 +11,8 @@ const courtDetailsQuery = graphql(` minStake alpha numberDisputes + numberClosedDisputes + numberAppealingDisputes numberStakedJurors stake paidETH diff --git a/web/src/hooks/queries/useMyAppealCasesQuery.ts b/web/src/hooks/queries/useMyAppealCasesQuery.ts index 3e9d5835a..1a379f500 100644 --- a/web/src/hooks/queries/useMyAppealCasesQuery.ts +++ b/web/src/hooks/queries/useMyAppealCasesQuery.ts @@ -1,7 +1,7 @@ import { graphql } from "src/graphql"; import { Address } from "viem"; import { useQuery } from "@tanstack/react-query"; -import { MyAppealCasesQuery, Period, Dispute_Filter } from "src/graphql/graphql"; +import { MyAppealCasesQuery, Period } from "src/graphql/graphql"; import { graphqlQueryFnHelper } from "utils/graphqlQueryFnHelper"; import { isUndefined } from "utils/index"; export type { MyAppealCasesQuery }; diff --git a/web/src/pages/Courts/TopSearch.tsx b/web/src/pages/Courts/TopSearch.tsx index 427f8e8d2..880174323 100644 --- a/web/src/pages/Courts/TopSearch.tsx +++ b/web/src/pages/Courts/TopSearch.tsx @@ -6,16 +6,9 @@ import { useCourtTree, CourtTreeQuery } from "queries/useCourtTree"; const TopSearch: React.FC = () => { const { data } = useCourtTree(); const navigate = useNavigate(); - const items = useMemo( - () => typeof data !== "undefined" && [rootToItems(data.court)], - [data] - ); + const items = useMemo(() => typeof data !== "undefined" && [rootToItems(data.court)], [data]); return items ? ( - navigate(path)} - placeholder="Select Court" - /> + navigate(path)} placeholder="Select Court" /> ) : ( <> ); @@ -30,10 +23,7 @@ interface IItem { const rootToItems = (court: CourtTreeQuery["court"]): IItem => ({ label: court!.name ? court!.name : "Unnamed Court", value: `/courts/${court!.id}`, - children: - court!.children.length > 0 - ? court!.children.map((child) => rootToItems(child)) - : undefined, + children: court!.children.length > 0 ? court!.children.map((child) => rootToItems(child)) : undefined, }); export default TopSearch; diff --git a/web/src/pages/Dashboard/index.tsx b/web/src/pages/Dashboard/index.tsx index 419984750..9db8898ba 100644 --- a/web/src/pages/Dashboard/index.tsx +++ b/web/src/pages/Dashboard/index.tsx @@ -8,7 +8,6 @@ import JurorInfo from "./JurorInfo"; import Courts from "./Courts"; import CasesDisplay from "components/CasesDisplay"; import ConnectWallet from "components/ConnectWallet"; -import { useMyAppealCasesQuery } from "~src/hooks/queries/useMyAppealCasesQuery"; const Container = styled.div` width: 100%; @@ -36,7 +35,6 @@ const Dashboard: React.FC = () => { const { data: disputesData } = useMyCasesQuery(address, casesPerPage * (currentPage - 1)); const { data: userData } = useUserQuery(address); const { setIsDashboard } = useFiltersContext(); - const { data: appealingCases } = useMyAppealCasesQuery(address); useEffect(() => { setIsDashboard(true); diff --git a/web/src/utils/graphqlQueryFnHelper.ts b/web/src/utils/graphqlQueryFnHelper.ts index 20aa7df41..2fad015c2 100644 --- a/web/src/utils/graphqlQueryFnHelper.ts +++ b/web/src/utils/graphqlQueryFnHelper.ts @@ -24,7 +24,7 @@ const CHAINID_TO_DISPUTE_TEMPLATE_SUBGRAPH = { export const graphqlUrl = (isDisputeTemplate = false, chainId = 421613) => { const coreUrl = DEPLOYMENTS_TO_KLEROS_CORE_SUBGRAPHS[DEPLOYMENT] ?? - "https://api.thegraph.com/subgraphs/name/alcercu/kleroscoretest"; + "https://api.thegraph.com/subgraphs/name/nhestrompia/kleros-v2-core-devnet-test"; return isDisputeTemplate ? CHAINID_TO_DISPUTE_TEMPLATE_SUBGRAPH[chainId] : coreUrl; }; From 40ec2ca0436e8c2af35ba01716c0eeacfc390a80 Mon Sep 17 00:00:00 2001 From: nhestrompia Date: Mon, 11 Sep 2023 05:17:35 +0300 Subject: [PATCH 10/36] refactor: code smell and some refactor --- web/src/components/CasesDisplay/CasesGrid.tsx | 51 ++++++------------- web/src/components/CasesDisplay/Search.tsx | 7 ++- web/src/context/FilterProvider.tsx | 11 ++-- 3 files changed, 27 insertions(+), 42 deletions(-) diff --git a/web/src/components/CasesDisplay/CasesGrid.tsx b/web/src/components/CasesDisplay/CasesGrid.tsx index 47ecdca7c..87113c915 100644 --- a/web/src/components/CasesDisplay/CasesGrid.tsx +++ b/web/src/components/CasesDisplay/CasesGrid.tsx @@ -52,42 +52,21 @@ const calculatePages = ( let totalPages = 0; - if ("counter" in data) { - const counterQueryData = data as CounterQuery; - const counter = counterQueryData.counter; - if (counter) { - switch (status) { - case 1: - totalPages = counter.cases - counter.casesRuled; - break; - case 2: - totalPages = counter.casesRuled; - break; - case 3: - totalPages = counter.casesAppealing; - break; - default: - totalPages = numberDisputes ?? 0; - } - } - } else { - const userQueryData = data as UserQuery; - const user = userQueryData.user; - if (user) { - switch (status) { - case 1: - totalPages = (user.totalDisputes ?? 0) - (user.totalResolvedDisputes ?? 0); - break; - case 2: - totalPages = user.totalResolvedDisputes ?? 0; - break; - case 3: - totalPages = myAppeals ?? 0; - break; - default: - totalPages = user.totalDisputes ?? 0; - } - } + switch (status) { + case 1: + totalPages = + "counter" in data + ? data?.counter?.cases - data?.counter?.casesRuled + : (data as UserQuery).user?.totalDisputes - (data as UserQuery).user?.totalResolvedDisputes; + break; + case 2: + totalPages = "counter" in data ? data?.counter?.casesRuled : (data as UserQuery).user?.totalResolvedDisputes; + break; + case 3: + totalPages = "counter" in data ? data?.counter?.casesAppealing : myAppeals ?? 0; + break; + default: + totalPages = "counter" in data ? numberDisputes ?? 0 : (data as UserQuery).user?.totalDisputes ?? 0; } return totalPages / casesPerPage; diff --git a/web/src/components/CasesDisplay/Search.tsx b/web/src/components/CasesDisplay/Search.tsx index 6d059e3a8..2a37c3a09 100644 --- a/web/src/components/CasesDisplay/Search.tsx +++ b/web/src/components/CasesDisplay/Search.tsx @@ -27,7 +27,12 @@ const Search: React.FC = () => { return (
- setSearch(e.target.value)} /> + setSearch(e.target.value)} + /> = ({ child const { data } = useCasesQuery(disputeSkip, combinedQueryFilters, direction); const { data: dashboardData } = useMyCasesQuery(address, disputeSkip, combinedQueryFilters, direction); - const disputes = isDashboard - ? (dashboardData?.user?.disputes as DisputeDetailsFragment[]) - : (data?.disputes as DisputeDetailsFragment[]); - useEffect(() => { setCurrentPage(1); }, [statusFilter]); useEffect(() => { + const disputes = isDashboard + ? (dashboardData?.user?.disputes as DisputeDetailsFragment[]) + : (data?.disputes as DisputeDetailsFragment[]); + if (search !== "" || statusFilter !== 0 || courtFilter !== 0 || timeFilter !== 0) { setIsFilterApplied(true); } else { setIsFilterApplied(false); } + setFilteredCases(disputes); - }, [debouncedSearch, data, timeFilter, courtFilter, statusFilter]); + }, [debouncedSearch, data, timeFilter, courtFilter, statusFilter, dashboardData]); const value = useMemo( () => ({ From 041c94510d716fb426c5049d885ff93f2ea05477 Mon Sep 17 00:00:00 2001 From: nhestrompia Date: Mon, 11 Sep 2023 05:36:42 +0300 Subject: [PATCH 11/36] fix: page number and query param --- web/src/context/FilterProvider.tsx | 2 +- web/src/hooks/queries/useMyAppealCasesQuery.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/web/src/context/FilterProvider.tsx b/web/src/context/FilterProvider.tsx index c72e8e927..2f3c25f59 100644 --- a/web/src/context/FilterProvider.tsx +++ b/web/src/context/FilterProvider.tsx @@ -98,7 +98,7 @@ export const FilterProvider: React.FC<{ children?: React.ReactNode }> = ({ child useEffect(() => { setCurrentPage(1); - }, [statusFilter]); + }, [statusFilter, courtFilter]); useEffect(() => { const disputes = isDashboard diff --git a/web/src/hooks/queries/useMyAppealCasesQuery.ts b/web/src/hooks/queries/useMyAppealCasesQuery.ts index 1a379f500..2758b430c 100644 --- a/web/src/hooks/queries/useMyAppealCasesQuery.ts +++ b/web/src/hooks/queries/useMyAppealCasesQuery.ts @@ -10,7 +10,7 @@ const myAppealCases = graphql(` query MyAppealCases($id: ID!, $where: Dispute_filter) { user(id: $id) { disputes(orderBy: lastPeriodChange, where: $where) { - period + id } } } From 23c21d892eccb9826f0d83167728da953f953808 Mon Sep 17 00:00:00 2001 From: alcercu <333aleix333@gmail.com> Date: Wed, 13 Sep 2023 18:01:23 +0200 Subject: [PATCH 12/36] refactor(subgraph): avoid contract binding and add numberVotingCases --- subgraph/schema.graphql | 2 ++ subgraph/src/KlerosCore.ts | 27 +++++++++++---------------- subgraph/src/entities/Court.ts | 1 + subgraph/subgraph.yaml | 2 +- 4 files changed, 15 insertions(+), 17 deletions(-) diff --git a/subgraph/schema.graphql b/subgraph/schema.graphql index d1aafe19b..84d4d5733 100644 --- a/subgraph/schema.graphql +++ b/subgraph/schema.graphql @@ -129,6 +129,7 @@ type Court @entity { disputes: [Dispute!]! @derivedFrom(field: "court") numberDisputes: BigInt! numberClosedDisputes: BigInt! + numberVotingDisputes: BigInt! numberAppealingDisputes: BigInt! stakedJurors: [JurorTokensPerCourt!]! @derivedFrom(field: "court") numberStakedJurors: BigInt! @@ -169,6 +170,7 @@ type Round @entity { } type Draw @entity { +type Draw @entity(immutable: true) { id: ID! # dispute.id-currentRound-voteID dispute: Dispute! round: Round! diff --git a/subgraph/src/KlerosCore.ts b/subgraph/src/KlerosCore.ts index 03f10cbd7..ce83e952d 100644 --- a/subgraph/src/KlerosCore.ts +++ b/subgraph/src/KlerosCore.ts @@ -84,21 +84,23 @@ export function handleDisputeCreation(event: DisputeCreation): void { } export function handleNewPeriod(event: NewPeriod): void { - const contract = KlerosCore.bind(event.address); const disputeID = event.params._disputeID; const dispute = Dispute.load(disputeID.toString()); - const disputeStorage = contract.disputes(disputeID); - const courtID = disputeStorage.value0.toString(); - const court = Court.load(courtID); if (!dispute) return; + const court = Court.load(dispute.court); if (!court) return; - const newPeriod = getPeriodName(event.params._period); + if (dispute.period === "vote") { + court.numberVotingDisputes = court.numberVotingDisputes.minus(ONE); updateCasesVoting(BigInt.fromI32(-1), event.block.timestamp); - } else if (newPeriod === "evidence") { + } else if (dispute.period === "appeal") { court.numberAppealingDisputes = court.numberAppealingDisputes.minus(ONE); updateCasesAppealing(BigInt.fromI32(-1), event.block.timestamp); - } else if (newPeriod === "vote") { + } + + const newPeriod = getPeriodName(event.params._period); + if (newPeriod === "vote") { + court.numberVotingDisputes = court.numberVotingDisputes.plus(ONE); updateCasesVoting(ONE, event.block.timestamp); } else if (newPeriod === "appeal") { court.numberAppealingDisputes = court.numberAppealingDisputes.plus(ONE); @@ -109,9 +111,6 @@ export function handleNewPeriod(event: NewPeriod): void { dispute.currentRuling = currentRulingInfo.getRuling(); dispute.overridden = currentRulingInfo.getOverridden(); dispute.tied = currentRulingInfo.getTied(); - dispute.save(); - court.numberAppealingDisputes = court.numberAppealingDisputes.minus(ONE); - updateCasesAppealing(BigInt.fromI32(-1), event.block.timestamp); } dispute.period = newPeriod; @@ -121,16 +120,13 @@ export function handleNewPeriod(event: NewPeriod): void { } export function handleRuling(event: Ruling): void { - const contract = KlerosCore.bind(event.address); + updateCasesRuled(ONE, event.block.timestamp); const disputeID = event.params._disputeID; const dispute = Dispute.load(disputeID.toString()); - const disputeStorage = contract.disputes(disputeID); - const courtID = disputeStorage.value0.toString(); - const court = Court.load(courtID); if (!dispute) return; dispute.ruled = true; dispute.save(); - updateCasesRuled(ONE, event.block.timestamp); + const court = Court.load(dispute.court); if (!court) return; court.numberClosedDisputes = court.numberClosedDisputes.plus(ONE); court.save(); @@ -146,7 +142,6 @@ export function handleAppealDecision(event: AppealDecision): void { dispute.currentRoundIndex = newRoundIndex; dispute.currentRound = roundID; dispute.save(); - const feeForJuror = getFeeForJuror(dispute.court); const roundInfo = contract.getRoundInfo(disputeID, newRoundIndex); createRoundFromRoundInfo(disputeID, newRoundIndex, roundInfo); } diff --git a/subgraph/src/entities/Court.ts b/subgraph/src/entities/Court.ts index 895457846..3d9866f99 100644 --- a/subgraph/src/entities/Court.ts +++ b/subgraph/src/entities/Court.ts @@ -15,6 +15,7 @@ export function createCourtFromEvent(event: CourtCreated): void { court.supportedDisputeKits = event.params._supportedDisputeKits.map((value) => value.toString()); court.numberDisputes = ZERO; court.numberClosedDisputes = ZERO; + court.numberVotingDisputes = ZERO; court.numberAppealingDisputes = ZERO; court.numberStakedJurors = ZERO; court.stake = ZERO; diff --git a/subgraph/subgraph.yaml b/subgraph/subgraph.yaml index 04487470e..fd19eb740 100644 --- a/subgraph/subgraph.yaml +++ b/subgraph/subgraph.yaml @@ -48,7 +48,7 @@ dataSources: handler: handleDisputeKitEnabled - event: StakeSet(indexed address,uint256,uint256) handler: handleStakeSet - - event: StakeDelayed(indexed address,uint256,uint256) + - event: StakeDelayed(indexed address,uint256,uint256,uint256) handler: handleStakeDelayed - event: TokenAndETHShift(indexed address,indexed uint256,indexed uint256,uint256,int256,int256,address) handler: handleTokenAndETHShift From efe885256275374bf90ec673c2402262065bc17a Mon Sep 17 00:00:00 2001 From: alcercu <333aleix333@gmail.com> Date: Wed, 13 Sep 2023 17:49:31 +0200 Subject: [PATCH 13/36] feat(subgraph): add blocknumber fields --- subgraph/schema.graphql | 3 ++- subgraph/src/KlerosCore.ts | 1 + subgraph/src/entities/Dispute.ts | 1 + subgraph/src/entities/Draw.ts | 1 + 4 files changed, 5 insertions(+), 1 deletion(-) diff --git a/subgraph/schema.graphql b/subgraph/schema.graphql index 84d4d5733..fbe849b97 100644 --- a/subgraph/schema.graphql +++ b/subgraph/schema.graphql @@ -149,6 +149,7 @@ type Dispute @entity { tied: Boolean! overridden: Boolean! lastPeriodChange: BigInt! + lastPeriodChangeBlockNumber: BigInt! rounds: [Round!]! @derivedFrom(field: "dispute") currentRound: Round! currentRoundIndex: BigInt! @@ -169,9 +170,9 @@ type Round @entity { feeToken: FeeToken } -type Draw @entity { type Draw @entity(immutable: true) { id: ID! # dispute.id-currentRound-voteID + blockNumber: BigInt! dispute: Dispute! round: Round! juror: User! diff --git a/subgraph/src/KlerosCore.ts b/subgraph/src/KlerosCore.ts index ce83e952d..9c96d6dcd 100644 --- a/subgraph/src/KlerosCore.ts +++ b/subgraph/src/KlerosCore.ts @@ -115,6 +115,7 @@ export function handleNewPeriod(event: NewPeriod): void { dispute.period = newPeriod; dispute.lastPeriodChange = event.block.timestamp; + dispute.lastPeriodChangeBlockNumber = event.block.number; dispute.save(); court.save(); } diff --git a/subgraph/src/entities/Dispute.ts b/subgraph/src/entities/Dispute.ts index a9d58b423..22936d038 100644 --- a/subgraph/src/entities/Dispute.ts +++ b/subgraph/src/entities/Dispute.ts @@ -15,6 +15,7 @@ export function createDisputeFromEvent(event: DisputeCreation): void { dispute.tied = true; dispute.overridden = false; dispute.lastPeriodChange = event.block.timestamp; + dispute.lastPeriodChangeBlockNumber = event.block.number; dispute.currentRoundIndex = ZERO; const roundID = `${disputeID.toString()}-${ZERO.toString()}`; dispute.currentRound = roundID; diff --git a/subgraph/src/entities/Draw.ts b/subgraph/src/entities/Draw.ts index fc06dbf3d..d5fd35946 100644 --- a/subgraph/src/entities/Draw.ts +++ b/subgraph/src/entities/Draw.ts @@ -8,6 +8,7 @@ export function createDrawFromEvent(event: DrawEvent): void { const voteID = event.params._voteID; const drawID = `${disputeID}-${roundIndex.toString()}-${voteID.toString()}`; const draw = new Draw(drawID); + draw.blockNumber = event.block.number; draw.dispute = disputeID; draw.round = roundID; draw.juror = event.params._address.toHexString(); From a3b16eda5ed06615e590bc62e6c39e81df9825a2 Mon Sep 17 00:00:00 2001 From: alcercu <333aleix333@gmail.com> Date: Wed, 13 Sep 2023 18:08:44 +0200 Subject: [PATCH 14/36] fix(web): add mainnet to chains, but in second position so it's not the default --- web/src/context/Web3Provider.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/src/context/Web3Provider.tsx b/web/src/context/Web3Provider.tsx index c6e161578..d47ddb479 100644 --- a/web/src/context/Web3Provider.tsx +++ b/web/src/context/Web3Provider.tsx @@ -8,7 +8,7 @@ import { jsonRpcProvider } from "wagmi/providers/jsonRpc"; import { useToggleTheme } from "hooks/useToggleThemeContext"; import { useTheme } from "styled-components"; -const chains = [arbitrumGoerli, gnosisChiado]; +const chains = [arbitrumGoerli, mainnet, gnosisChiado]; const projectId = process.env.WALLETCONNECT_PROJECT_ID ?? "6efaa26765fa742153baf9281e218217"; const { publicClient, webSocketPublicClient } = configureChains(chains, [ From 3045e6cc5e26df49450e856afe331f0ae9ace8e4 Mon Sep 17 00:00:00 2001 From: nhestrompia Date: Thu, 14 Sep 2023 06:43:37 +0300 Subject: [PATCH 15/36] refactor: fetching at pages --- web/.env.devnet.public | 2 +- web/src/components/CasesDisplay/CasesGrid.tsx | 61 +++------------ web/src/components/CasesDisplay/Search.tsx | 9 ++- web/src/components/CasesDisplay/index.tsx | 17 ++-- web/src/context/FilterProvider.tsx | 78 ++----------------- ...asesQuery.ts => useMyCasesCounterQuery.ts} | 8 +- web/src/pages/Cases/index.tsx | 64 ++++++++++++--- web/src/pages/Dashboard/index.tsx | 51 ++++++++++-- web/src/utils/graphqlQueryFnHelper.ts | 2 +- 9 files changed, 138 insertions(+), 154 deletions(-) rename web/src/hooks/queries/{useMyAppealCasesQuery.ts => useMyCasesCounterQuery.ts} (76%) diff --git a/web/.env.devnet.public b/web/.env.devnet.public index 23b19d862..727078abd 100644 --- a/web/.env.devnet.public +++ b/web/.env.devnet.public @@ -1,4 +1,4 @@ # Do not enter sensitive information here. export REACT_APP_DEPLOYMENT=devnet -export REACT_APP_KLEROS_CORE_SUBGRAPH_DEVNET=https://api.thegraph.com/subgraphs/name/nhestrompia/kleros-v2-core-devnet-test +export REACT_APP_KLEROS_CORE_SUBGRAPH_DEVNET=https://api.thegraph.com/subgraphs/name/alcercu/kleroscoretest export REACT_APP_DISPUTE_TEMPLATE_ARBGOERLI_SUBGRAPH_DEVNET=https://api.thegraph.com/subgraphs/name/alcercu/templateregistrydevnet \ No newline at end of file diff --git a/web/src/components/CasesDisplay/CasesGrid.tsx b/web/src/components/CasesDisplay/CasesGrid.tsx index 4e2d22980..7a3ec3a6b 100644 --- a/web/src/components/CasesDisplay/CasesGrid.tsx +++ b/web/src/components/CasesDisplay/CasesGrid.tsx @@ -1,16 +1,11 @@ import React from "react"; import styled from "styled-components"; -import { useAccount } from "wagmi"; import Skeleton from "react-loading-skeleton"; import { StandardPagination } from "@kleros/ui-components-library"; import { isUndefined } from "utils/index"; import { DisputeDetailsFragment } from "queries/useCasesQuery"; -import { useCounterQuery } from "hooks/queries/useCounter"; -import { useUserQuery } from "hooks/queries/useUser"; import { useFiltersContext } from "context/FilterProvider"; import DisputeCard from "components/DisputeCard"; -import { CounterQuery, UserQuery } from "src/graphql/graphql"; -import { useMyAppealCasesQuery } from "~src/hooks/queries/useMyAppealCasesQuery"; const Container = styled.div` display: flex; @@ -33,60 +28,28 @@ const StyledPagination = styled(StandardPagination)` export interface ICasesGrid { disputes?: DisputeDetailsFragment[]; + numberDisputes?: number; currentPage: number; setCurrentPage: (newPage: number) => void; - numberDisputes?: number; casesPerPage: number; + totalPages: number; } -const calculatePages = ( - status: number, - data: CounterQuery | UserQuery | undefined, - casesPerPage: number, - numberDisputes: number, - myAppeals?: number -) => { - if (!data) { - return 0; - } - - let totalPages = 0; - - switch (status) { - case 1: - totalPages = - "counter" in data - ? data?.counter?.cases - data?.counter?.casesRuled - : (data as UserQuery).user?.totalDisputes - (data as UserQuery).user?.totalResolvedDisputes; - break; - case 2: - totalPages = "counter" in data ? data?.counter?.casesRuled : (data as UserQuery).user?.totalResolvedDisputes; - break; - case 3: - totalPages = "counter" in data ? data?.counter?.casesAppealing : myAppeals ?? 0; - break; - default: - totalPages = "counter" in data ? numberDisputes ?? 0 : (data as UserQuery).user?.totalDisputes ?? 0; - } - - return totalPages / casesPerPage; -}; +const CasesGrid: React.FC = ({ + disputes, + numberDisputes, + casesPerPage, + totalPages, + currentPage, + setCurrentPage, +}) => { + const { debouncedSearch } = useFiltersContext(); -const CasesGrid: React.FC = ({ disputes, currentPage, setCurrentPage, numberDisputes, casesPerPage }) => { - const { address } = useAccount(); - const { statusFilter, debouncedSearch, filteredCases, isDashboard } = useFiltersContext(); - const { data: userData } = useUserQuery(address); - const { data: userAppealCases } = useMyAppealCasesQuery(address); - const { data: counterData } = useCounterQuery(); - const userAppealCasesNumber = userAppealCases?.user?.disputes.length; - const totalPages = isDashboard - ? calculatePages(statusFilter, userData, casesPerPage, numberDisputes ?? 0, userAppealCasesNumber) - : calculatePages(statusFilter, counterData, casesPerPage, numberDisputes ?? 0); return ( <> {!isUndefined(numberDisputes) && ( - {isUndefined(disputes) || isUndefined(filteredCases) + {isUndefined(disputes) ? [...Array(casesPerPage)].map((_, i) => ) : disputes.map((dispute, i) => { return ; diff --git a/web/src/components/CasesDisplay/Search.tsx b/web/src/components/CasesDisplay/Search.tsx index 2a37c3a09..b697b0414 100644 --- a/web/src/components/CasesDisplay/Search.tsx +++ b/web/src/components/CasesDisplay/Search.tsx @@ -2,6 +2,7 @@ import React from "react"; import styled from "styled-components"; import { Searchbar, DropdownCascader } from "@kleros/ui-components-library"; import { useFiltersContext } from "context/FilterProvider"; + const Container = styled.div` display: flex; flex-wrap: wrap; @@ -21,8 +22,12 @@ const StyledSearchbar = styled(Searchbar)` } `; -const Search: React.FC = () => { - const { search, setSearch, setCourtFilter } = useFiltersContext(); +interface ISearch { + setCourtFilter: (arg0: number) => void; +} + +const Search: React.FC = ({ setCourtFilter }) => { + const { search, setSearch } = useFiltersContext(); return (
diff --git a/web/src/components/CasesDisplay/index.tsx b/web/src/components/CasesDisplay/index.tsx index 468268cf6..ae81409dc 100644 --- a/web/src/components/CasesDisplay/index.tsx +++ b/web/src/components/CasesDisplay/index.tsx @@ -3,7 +3,6 @@ import styled from "styled-components"; import Search from "./Search"; import StatsAndFilters from "./StatsAndFilters"; import CasesGrid, { ICasesGrid } from "./CasesGrid"; -import { useFiltersContext } from "context/FilterProvider"; const StyledHR = styled.hr` margin-top: 24px; @@ -14,6 +13,7 @@ interface ICasesDisplay extends ICasesGrid { numberClosedDisputes?: number; title?: string; className?: string; + setCourtFilter: (arg0: number) => void; } const CasesDisplay: React.FC = ({ @@ -25,26 +25,27 @@ const CasesDisplay: React.FC = ({ casesPerPage, title = "Cases", className, + totalPages, + setCourtFilter, }) => { - const { filteredCases, isFilterApplied } = useFiltersContext(); - return (

{title}

- + - {isFilterApplied && filteredCases?.length === 0 ? ( + {disputes?.length === 0 ? (

No cases found

) : ( 0 ? filteredCases : disputes} + disputes={disputes} {...{ - currentPage, - setCurrentPage, numberDisputes, casesPerPage, + totalPages, + currentPage, + setCurrentPage, }} /> )} diff --git a/web/src/context/FilterProvider.tsx b/web/src/context/FilterProvider.tsx index 2f3c25f59..7f9c7ddda 100644 --- a/web/src/context/FilterProvider.tsx +++ b/web/src/context/FilterProvider.tsx @@ -1,27 +1,16 @@ -import React, { useState, createContext, useContext, useMemo, useEffect } from "react"; +import React, { useState, createContext, useContext, useMemo } from "react"; import { useDebounce } from "react-use"; -import { useAccount } from "wagmi"; -import { DisputeDetailsFragment, useMyCasesQuery } from "queries/useCasesQuery"; -import { useCasesQuery } from "hooks/queries/useCasesQuery"; -import { OrderDirection, Period } from "src/graphql/graphql"; +import { Dispute_Filter, Period } from "src/graphql/graphql"; interface IFilters { search: string; setSearch: (arg0: string) => void; debouncedSearch: string; statusFilter: number; - courtFilter: number; - filteredCases: DisputeDetailsFragment[]; - setFilteredCases: (arg0: DisputeDetailsFragment[]) => void; - setCourtFilter: (arg0: number) => void; timeFilter: number | string; setTimeFilter: (arg0: number | string) => void; - currentPage: number; - setCurrentPage: (arg0: number) => void; - isDashboard: boolean; - setIsDashboard: (arg0: boolean) => void; + combinedQueryFilters: Dispute_Filter; setStatusFilter: (arg0: number) => void; - isFilterApplied: boolean; } const Context = createContext({ @@ -34,27 +23,12 @@ const Context = createContext({ setStatusFilter: () => { // }, - isDashboard: false, - setIsDashboard: () => { - // - }, - courtFilter: 0, - setCourtFilter: () => { - // - }, timeFilter: 0, setTimeFilter: () => { // }, - filteredCases: [], - setFilteredCases: () => { - // - }, - currentPage: 1, - setCurrentPage: () => { - // - }, - isFilterApplied: false, + + combinedQueryFilters: {}, }); const getStatusPeriod = (statusFilter: number) => { @@ -71,69 +45,31 @@ const getStatusPeriod = (statusFilter: number) => { }; export const FilterProvider: React.FC<{ children?: React.ReactNode }> = ({ children }) => { - const { address } = useAccount(); - const [isFilterApplied, setIsFilterApplied] = useState(false); - const [currentPage, setCurrentPage] = useState(1); const [statusFilter, setStatusFilter] = useState(0); - const [courtFilter, setCourtFilter] = useState(0); - const [isDashboard, setIsDashboard] = useState(false); const [timeFilter, setTimeFilter] = useState(0); const [search, setSearch] = useState(""); - const [filteredCases, setFilteredCases] = useState([]); const [debouncedSearch, setDebouncedSearch] = useState(""); useDebounce(() => setDebouncedSearch(search), 500, [search]); - const direction = timeFilter === 0 ? OrderDirection.Desc : OrderDirection.Asc; - const disputeSkip = debouncedSearch ? 0 : 3 * (currentPage - 1); const periodStatus = getStatusPeriod(statusFilter); const queryFilter = debouncedSearch ? { id: debouncedSearch } : undefined; - const courtChoice = courtFilter === 0 ? {} : { court: courtFilter.toString() }; const combinedQueryFilters = { ...queryFilter, ...periodStatus, - ...courtChoice, }; - const { data } = useCasesQuery(disputeSkip, combinedQueryFilters, direction); - const { data: dashboardData } = useMyCasesQuery(address, disputeSkip, combinedQueryFilters, direction); - - useEffect(() => { - setCurrentPage(1); - }, [statusFilter, courtFilter]); - - useEffect(() => { - const disputes = isDashboard - ? (dashboardData?.user?.disputes as DisputeDetailsFragment[]) - : (data?.disputes as DisputeDetailsFragment[]); - - if (search !== "" || statusFilter !== 0 || courtFilter !== 0 || timeFilter !== 0) { - setIsFilterApplied(true); - } else { - setIsFilterApplied(false); - } - - setFilteredCases(disputes); - }, [debouncedSearch, data, timeFilter, courtFilter, statusFilter, dashboardData]); const value = useMemo( () => ({ search, setSearch, debouncedSearch, - courtFilter, - setCourtFilter, timeFilter, setTimeFilter, statusFilter, setStatusFilter, - filteredCases, - setFilteredCases, - currentPage, - setCurrentPage, - isDashboard, - setIsDashboard, - isFilterApplied, + combinedQueryFilters, }), - [search, debouncedSearch, timeFilter, statusFilter, filteredCases] + [search, debouncedSearch, timeFilter, statusFilter] ); return {children}; }; diff --git a/web/src/hooks/queries/useMyAppealCasesQuery.ts b/web/src/hooks/queries/useMyCasesCounterQuery.ts similarity index 76% rename from web/src/hooks/queries/useMyAppealCasesQuery.ts rename to web/src/hooks/queries/useMyCasesCounterQuery.ts index 2758b430c..74b069149 100644 --- a/web/src/hooks/queries/useMyAppealCasesQuery.ts +++ b/web/src/hooks/queries/useMyCasesCounterQuery.ts @@ -1,7 +1,7 @@ import { graphql } from "src/graphql"; import { Address } from "viem"; import { useQuery } from "@tanstack/react-query"; -import { MyAppealCasesQuery, Period } from "src/graphql/graphql"; +import { MyAppealCasesQuery, Dispute_Filter } from "src/graphql/graphql"; import { graphqlQueryFnHelper } from "utils/graphqlQueryFnHelper"; import { isUndefined } from "utils/index"; export type { MyAppealCasesQuery }; @@ -16,16 +16,16 @@ const myAppealCases = graphql(` } `); -export const useMyAppealCasesQuery = (user?: Address) => { +export const useMyCasesCounterQuery = (user?: Address, where?: Dispute_Filter) => { const isEnabled = !isUndefined(user); return useQuery({ - queryKey: [`useMyAppealCasesQuery`, user], + queryKey: [`useMyCasesCounterQuery`, user], enabled: isEnabled, queryFn: async () => await graphqlQueryFnHelper(myAppealCases, { id: user?.toLowerCase(), - where: { period: Period.Appeal }, + where, }), }); }; diff --git a/web/src/pages/Cases/index.tsx b/web/src/pages/Cases/index.tsx index 5f13e2819..2d137f5ab 100644 --- a/web/src/pages/Cases/index.tsx +++ b/web/src/pages/Cases/index.tsx @@ -1,10 +1,11 @@ -import React, { useEffect } from "react"; +import React, { useState, useEffect } from "react"; import styled from "styled-components"; import { Routes, Route } from "react-router-dom"; -import { useAccount } from "wagmi"; import { useFiltersContext } from "context/FilterProvider"; import { DisputeDetailsFragment, useCasesQuery } from "queries/useCasesQuery"; +import { useCourtDetails, CourtDetailsQuery } from "queries/useCourtDetails"; import { useCounterQuery } from "queries/useCounter"; +import { CounterQuery, OrderDirection } from "src/graphql/graphql"; import CasesDisplay from "components/CasesDisplay"; import CaseDetails from "./CaseDetails"; @@ -15,18 +16,60 @@ const Container = styled.div` padding: 32px; `; +export const calculatePages = ( + status: number, + data: CounterQuery | CourtDetailsQuery | undefined, + casesPerPage: number, + numberDisputes: number +) => { + if (!data) { + return 0; + } + + let totalPages = 0; + + switch (status) { + case 1: + totalPages = + "counter" in data + ? data?.counter?.cases - data?.counter?.casesRuled + : (data as CourtDetailsQuery).court?.numberDisputes - (data as CourtDetailsQuery).court?.numberClosedDisputes; + break; + case 2: + totalPages = + "counter" in data ? data?.counter?.casesRuled : (data as CourtDetailsQuery).court?.numberClosedDisputes; + break; + case 3: + totalPages = + "counter" in data ? data?.counter?.casesAppealing : (data as CourtDetailsQuery).court?.numberAppealingDisputes; + break; + default: + totalPages = "counter" in data ? numberDisputes ?? 0 : (data as CourtDetailsQuery).court?.numberDisputes; + } + + return totalPages / casesPerPage; +}; + const Cases: React.FC = () => { - const { isConnected } = useAccount(); const casesPerPage = 3; - const { setCurrentPage, currentPage } = useFiltersContext(); - const { data } = useCasesQuery(casesPerPage * (currentPage - 1)); + const { combinedQueryFilters, debouncedSearch, timeFilter, statusFilter } = useFiltersContext(); + const [currentPage, setCurrentPage] = useState(1); + const [courtFilter, setCourtFilter] = useState(0); const { data: counterData } = useCounterQuery(); - - const { setIsDashboard } = useFiltersContext(); + const { data: courtData } = useCourtDetails(courtFilter.toString()); + const direction = timeFilter === 0 ? OrderDirection.Desc : OrderDirection.Asc; + const courtChoice = courtFilter === 0 ? {} : { court: courtFilter.toString() }; + const disputeSkip = debouncedSearch ? 0 : 3 * (currentPage - 1); + const queryFilters = { ...combinedQueryFilters, ...courtChoice }; + const { data } = useCasesQuery(disputeSkip, queryFilters, direction); + const totalPages = + courtFilter !== 0 + ? calculatePages(statusFilter, courtData, casesPerPage, courtData?.court?.numberDisputes) + : calculatePages(statusFilter, counterData, casesPerPage, counterData?.counter?.cases); useEffect(() => { - setIsDashboard(false); - }, [isConnected]); + setCurrentPage(1); + }, [statusFilter, courtFilter]); return ( @@ -38,7 +81,8 @@ const Cases: React.FC = () => { disputes={data?.disputes as DisputeDetailsFragment[]} numberDisputes={counterData?.counter?.cases} numberClosedDisputes={counterData?.counter?.casesRuled} - {...{ currentPage, setCurrentPage, casesPerPage }} + totalPages={totalPages} + {...{ casesPerPage, currentPage, setCurrentPage, setCourtFilter }} /> } /> diff --git a/web/src/pages/Dashboard/index.tsx b/web/src/pages/Dashboard/index.tsx index 9db8898ba..f7ee2db5e 100644 --- a/web/src/pages/Dashboard/index.tsx +++ b/web/src/pages/Dashboard/index.tsx @@ -1,9 +1,11 @@ -import React, { useEffect } from "react"; +import React, { useState, useEffect } from "react"; import styled from "styled-components"; import { useAccount } from "wagmi"; import { DisputeDetailsFragment, useMyCasesQuery } from "queries/useCasesQuery"; import { useFiltersContext } from "context/FilterProvider"; -import { useUserQuery } from "queries/useUser"; +import { useMyCasesCounterQuery } from "queries/useMyCasesCounterQuery"; +import { useUserQuery, UserQuery } from "queries/useUser"; +import { OrderDirection, Period } from "src/graphql/graphql"; import JurorInfo from "./JurorInfo"; import Courts from "./Courts"; import CasesDisplay from "components/CasesDisplay"; @@ -28,17 +30,49 @@ const ConnectWalletContainer = styled.div` color: ${({ theme }) => theme.primaryText}; `; +const calculatePages = (status: number, data: UserQuery | undefined, casesPerPage: number, myAppeals?: number) => { + if (!data) { + return 0; + } + + let totalPages = 0; + + switch (status) { + case 1: + totalPages = data.user?.totalDisputes - data.user?.totalResolvedDisputes; + break; + case 2: + totalPages = data.user?.totalResolvedDisputes ?? 0; + break; + case 3: + totalPages = myAppeals ?? 0; + break; + default: + totalPages = data.user?.totalDisputes ?? 0; + } + + return totalPages / casesPerPage; +}; + const Dashboard: React.FC = () => { const { isConnected, address } = useAccount(); - const { currentPage, setCurrentPage } = useFiltersContext(); + const { combinedQueryFilters, debouncedSearch, timeFilter, statusFilter } = useFiltersContext(); + const [currentPage, setCurrentPage] = useState(1); + const [courtFilter, setCourtFilter] = useState(0); const casesPerPage = 3; - const { data: disputesData } = useMyCasesQuery(address, casesPerPage * (currentPage - 1)); + const disputeSkip = debouncedSearch ? 0 : 3 * (currentPage - 1); + const direction = timeFilter === 0 ? OrderDirection.Desc : OrderDirection.Asc; + const courtChoice = courtFilter === 0 ? {} : { court: courtFilter.toString() }; + const { data: userAppealCases } = useMyCasesCounterQuery(address, { period: Period.Appeal, ...courtChoice }); + const userAppealCasesNumber = userAppealCases?.user?.disputes.length; + const queryFilters = { ...combinedQueryFilters, ...courtChoice }; + const { data: disputesData } = useMyCasesQuery(address, disputeSkip, queryFilters, direction); const { data: userData } = useUserQuery(address); - const { setIsDashboard } = useFiltersContext(); + const totalPages = calculatePages(statusFilter, userData, casesPerPage, userAppealCasesNumber); useEffect(() => { - setIsDashboard(true); - }, [isConnected]); + setCurrentPage(1); + }, [statusFilter, courtFilter]); return ( @@ -51,7 +85,8 @@ const Dashboard: React.FC = () => { disputes={disputesData?.user?.disputes as DisputeDetailsFragment[]} numberDisputes={userData?.user?.totalDisputes} numberClosedDisputes={userData?.user?.totalResolvedDisputes} - {...{ currentPage, setCurrentPage, casesPerPage }} + totalPages={totalPages} + {...{ casesPerPage, currentPage, setCurrentPage, setCourtFilter }} /> ) : ( diff --git a/web/src/utils/graphqlQueryFnHelper.ts b/web/src/utils/graphqlQueryFnHelper.ts index 2fad015c2..20aa7df41 100644 --- a/web/src/utils/graphqlQueryFnHelper.ts +++ b/web/src/utils/graphqlQueryFnHelper.ts @@ -24,7 +24,7 @@ const CHAINID_TO_DISPUTE_TEMPLATE_SUBGRAPH = { export const graphqlUrl = (isDisputeTemplate = false, chainId = 421613) => { const coreUrl = DEPLOYMENTS_TO_KLEROS_CORE_SUBGRAPHS[DEPLOYMENT] ?? - "https://api.thegraph.com/subgraphs/name/nhestrompia/kleros-v2-core-devnet-test"; + "https://api.thegraph.com/subgraphs/name/alcercu/kleroscoretest"; return isDisputeTemplate ? CHAINID_TO_DISPUTE_TEMPLATE_SUBGRAPH[chainId] : coreUrl; }; From 6f2228d88254c4fb5bce63352f0d72b149209c40 Mon Sep 17 00:00:00 2001 From: nhestrompia Date: Thu, 14 Sep 2023 06:49:24 +0300 Subject: [PATCH 16/36] refactor: query naming --- web/src/hooks/queries/useMyCasesCounterQuery.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/web/src/hooks/queries/useMyCasesCounterQuery.ts b/web/src/hooks/queries/useMyCasesCounterQuery.ts index 74b069149..df891cf60 100644 --- a/web/src/hooks/queries/useMyCasesCounterQuery.ts +++ b/web/src/hooks/queries/useMyCasesCounterQuery.ts @@ -1,13 +1,13 @@ import { graphql } from "src/graphql"; import { Address } from "viem"; import { useQuery } from "@tanstack/react-query"; -import { MyAppealCasesQuery, Dispute_Filter } from "src/graphql/graphql"; +import { MyCasesCounterQuery, Dispute_Filter } from "src/graphql/graphql"; import { graphqlQueryFnHelper } from "utils/graphqlQueryFnHelper"; import { isUndefined } from "utils/index"; -export type { MyAppealCasesQuery }; +export type { MyCasesCounterQuery }; -const myAppealCases = graphql(` - query MyAppealCases($id: ID!, $where: Dispute_filter) { +const myCasesCounter = graphql(` + query MyCasesCounter($id: ID!, $where: Dispute_filter) { user(id: $id) { disputes(orderBy: lastPeriodChange, where: $where) { id @@ -19,11 +19,11 @@ const myAppealCases = graphql(` export const useMyCasesCounterQuery = (user?: Address, where?: Dispute_Filter) => { const isEnabled = !isUndefined(user); - return useQuery({ + return useQuery({ queryKey: [`useMyCasesCounterQuery`, user], enabled: isEnabled, queryFn: async () => - await graphqlQueryFnHelper(myAppealCases, { + await graphqlQueryFnHelper(myCasesCounter, { id: user?.toLowerCase(), where, }), From 28547bb01e3850f39dae924ae0d5df5e7a52a775 Mon Sep 17 00:00:00 2001 From: nhestrompia Date: Fri, 15 Sep 2023 12:32:49 +0300 Subject: [PATCH 17/36] fix: context states resetting --- web/src/app.tsx | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/web/src/app.tsx b/web/src/app.tsx index 594489272..76e04424a 100644 --- a/web/src/app.tsx +++ b/web/src/app.tsx @@ -1,5 +1,5 @@ import React from "react"; -import { Route } from "react-router-dom"; +import { Route, useLocation } from "react-router-dom"; import { SentryRoutes } from "./utils/sentry"; import "react-loading-skeleton/dist/skeleton.css"; import "react-toastify/dist/ReactToastify.css"; @@ -16,12 +16,15 @@ import Courts from "./pages/Courts"; import DisputeTemplateView from "./pages/DisputeTemplateView"; const App: React.FC = () => { + const location = useLocation(); + + const routeKey = location.pathname; return ( - + }> } /> From 5127f0fbf8cf4f88a58401d5504c73a8c15419d8 Mon Sep 17 00:00:00 2001 From: nhestrompia Date: Fri, 15 Sep 2023 16:01:03 +0300 Subject: [PATCH 18/36] refactor: clear naming and query params --- web/src/hooks/queries/useCounter.ts | 6 ++++++ web/src/pages/Cases/index.tsx | 12 ++++++------ web/src/pages/Dashboard/index.tsx | 12 ++++++------ web/src/styles/global-style.ts | 8 ++------ 4 files changed, 20 insertions(+), 18 deletions(-) diff --git a/web/src/hooks/queries/useCounter.ts b/web/src/hooks/queries/useCounter.ts index 131f9774e..e771d7c31 100644 --- a/web/src/hooks/queries/useCounter.ts +++ b/web/src/hooks/queries/useCounter.ts @@ -6,9 +6,15 @@ import { graphqlQueryFnHelper } from "utils/graphqlQueryFnHelper"; const counterQuery = graphql(` query Counter { counter(id: "0") { + id cases casesRuled + casesVoting casesAppealing + stakedPNK + redistributedPNK + paidETH + activeJurors } } `); diff --git a/web/src/pages/Cases/index.tsx b/web/src/pages/Cases/index.tsx index 2d137f5ab..9ebf43fc0 100644 --- a/web/src/pages/Cases/index.tsx +++ b/web/src/pages/Cases/index.tsx @@ -26,28 +26,28 @@ export const calculatePages = ( return 0; } - let totalPages = 0; + let totalCases = 0; switch (status) { case 1: - totalPages = + totalCases = "counter" in data ? data?.counter?.cases - data?.counter?.casesRuled : (data as CourtDetailsQuery).court?.numberDisputes - (data as CourtDetailsQuery).court?.numberClosedDisputes; break; case 2: - totalPages = + totalCases = "counter" in data ? data?.counter?.casesRuled : (data as CourtDetailsQuery).court?.numberClosedDisputes; break; case 3: - totalPages = + totalCases = "counter" in data ? data?.counter?.casesAppealing : (data as CourtDetailsQuery).court?.numberAppealingDisputes; break; default: - totalPages = "counter" in data ? numberDisputes ?? 0 : (data as CourtDetailsQuery).court?.numberDisputes; + totalCases = "counter" in data ? numberDisputes ?? 0 : (data as CourtDetailsQuery).court?.numberDisputes; } - return totalPages / casesPerPage; + return totalCases / casesPerPage; }; const Cases: React.FC = () => { diff --git a/web/src/pages/Dashboard/index.tsx b/web/src/pages/Dashboard/index.tsx index f7ee2db5e..5eea8c05c 100644 --- a/web/src/pages/Dashboard/index.tsx +++ b/web/src/pages/Dashboard/index.tsx @@ -35,23 +35,23 @@ const calculatePages = (status: number, data: UserQuery | undefined, casesPerPag return 0; } - let totalPages = 0; + let totalCases = 0; switch (status) { case 1: - totalPages = data.user?.totalDisputes - data.user?.totalResolvedDisputes; + totalCases = data.user?.totalDisputes - data.user?.totalResolvedDisputes; break; case 2: - totalPages = data.user?.totalResolvedDisputes ?? 0; + totalCases = data.user?.totalResolvedDisputes ?? 0; break; case 3: - totalPages = myAppeals ?? 0; + totalCases = myAppeals ?? 0; break; default: - totalPages = data.user?.totalDisputes ?? 0; + totalCases = data.user?.totalDisputes ?? 0; } - return totalPages / casesPerPage; + return totalCases / casesPerPage; }; const Dashboard: React.FC = () => { diff --git a/web/src/styles/global-style.ts b/web/src/styles/global-style.ts index 22bc268ba..48d9cb78a 100644 --- a/web/src/styles/global-style.ts +++ b/web/src/styles/global-style.ts @@ -10,8 +10,8 @@ export const GlobalStyle = createGlobalStyle` .react-loading-skeleton { z-index: 0; - --base-color: ${({ theme }) => theme.lightGrey}; - --highlight-color: ${({ theme }) => theme.stroke}; + --base-color: ${({ theme }) => theme.skeletonBackground}; + --highlight-color: ${({ theme }) => theme.skeletonHighlight}; } body { @@ -105,8 +105,4 @@ export const GlobalStyle = createGlobalStyle` } } - .react-loading-skeleton { - --base-color: ${({ theme }) => theme.skeletonBackground}; - --highlight-color: ${({ theme }) => theme.skeletonHighlight}; - } `; From a29a3d84cd2047a5e94748e4affbf292e0223c0d Mon Sep 17 00:00:00 2001 From: nhestrompia Date: Fri, 15 Sep 2023 16:33:48 +0300 Subject: [PATCH 19/36] fix: stake amount --- web/src/pages/Dashboard/Courts/CourtCard.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/web/src/pages/Dashboard/Courts/CourtCard.tsx b/web/src/pages/Dashboard/Courts/CourtCard.tsx index 2c1fbd147..500cfb0a4 100644 --- a/web/src/pages/Dashboard/Courts/CourtCard.tsx +++ b/web/src/pages/Dashboard/Courts/CourtCard.tsx @@ -52,7 +52,8 @@ const CourtCard: React.FC = ({ id, name }) => { const lockedStake = format(jurorBalance?.[1]); return ( - (stake || lockedStake) !== "0" && ( + stake !== "0" || + (lockedStake !== "0" && ( @@ -66,7 +67,7 @@ const CourtCard: React.FC = ({ id, name }) => { {`${lockedStake} PNK`} - ) + )) ); }; From 504d5d7cf207c7f150dee13293c41eb24aad9971 Mon Sep 17 00:00:00 2001 From: alcercu <333aleix333@gmail.com> Date: Fri, 15 Sep 2023 16:31:52 +0200 Subject: [PATCH 20/36] feat(subgraph): add totalAppealingDisputes for user entity --- subgraph/schema.graphql | 2 ++ subgraph/src/KlerosCore.ts | 14 +++++++++++++- subgraph/src/entities/User.ts | 1 + 3 files changed, 16 insertions(+), 1 deletion(-) diff --git a/subgraph/schema.graphql b/subgraph/schema.graphql index fbe849b97..2b58ea2bf 100644 --- a/subgraph/schema.graphql +++ b/subgraph/schema.graphql @@ -71,6 +71,7 @@ type User @entity { totalResolvedDisputes: BigInt! totalDisputes: BigInt! totalCoherent: BigInt! + totalAppealingDisputes: BigInt! votes: [Vote!]! @derivedFrom(field: "juror") contributions: [Contribution!]! @derivedFrom(field: "contributor") evidences: [Evidence!]! @derivedFrom(field: "sender") @@ -153,6 +154,7 @@ type Dispute @entity { rounds: [Round!]! @derivedFrom(field: "dispute") currentRound: Round! currentRoundIndex: BigInt! + jurors: [User!]! @derivedFrom(field: "disputes") shifts: [TokenAndETHShift!]! @derivedFrom(field: "dispute") disputeKitDispute: DisputeKitDispute @derivedFrom(field: "coreDispute") } diff --git a/subgraph/src/KlerosCore.ts b/subgraph/src/KlerosCore.ts index 9c96d6dcd..ca29acabf 100644 --- a/subgraph/src/KlerosCore.ts +++ b/subgraph/src/KlerosCore.ts @@ -25,7 +25,7 @@ import { updateJurorDelayedStake, updateJurorStake } from "./entities/JurorToken import { createDrawFromEvent } from "./entities/Draw"; import { updateTokenAndEthShiftFromEvent } from "./entities/TokenAndEthShift"; import { updateArbitrableCases } from "./entities/Arbitrable"; -import { Court, Dispute, FeeToken } from "../generated/schema"; +import { Court, Dispute, FeeToken, User } from "../generated/schema"; import { BigInt } from "@graphprotocol/graph-ts"; import { updatePenalty } from "./entities/Penalty"; import { ensureFeeToken } from "./entities/FeeToken"; @@ -94,6 +94,12 @@ export function handleNewPeriod(event: NewPeriod): void { court.numberVotingDisputes = court.numberVotingDisputes.minus(ONE); updateCasesVoting(BigInt.fromI32(-1), event.block.timestamp); } else if (dispute.period === "appeal") { + let juror: User; + for (let i = 0; i < dispute.jurors.entries.length; i++) { + juror = ensureUser(dispute.jurors.entries[0].value.toString()); + juror.totalAppealingDisputes = juror.totalAppealingDisputes.minus(ONE); + juror.save(); + } court.numberAppealingDisputes = court.numberAppealingDisputes.minus(ONE); updateCasesAppealing(BigInt.fromI32(-1), event.block.timestamp); } @@ -103,6 +109,12 @@ export function handleNewPeriod(event: NewPeriod): void { court.numberVotingDisputes = court.numberVotingDisputes.plus(ONE); updateCasesVoting(ONE, event.block.timestamp); } else if (newPeriod === "appeal") { + let juror: User; + for (let i = 0; i < dispute.jurors.entries.length; i++) { + juror = ensureUser(dispute.jurors.entries[0].value.toString()); + juror.totalAppealingDisputes = juror.totalAppealingDisputes.plus(ONE); + juror.save(); + } court.numberAppealingDisputes = court.numberAppealingDisputes.plus(ONE); updateCasesAppealing(ONE, event.block.timestamp); } else if (newPeriod === "execution") { diff --git a/subgraph/src/entities/User.ts b/subgraph/src/entities/User.ts index 1b97044ad..a1b0ed340 100644 --- a/subgraph/src/entities/User.ts +++ b/subgraph/src/entities/User.ts @@ -20,6 +20,7 @@ export function createUserFromAddress(id: string): User { user.disputes = []; user.resolvedDisputes = []; user.totalResolvedDisputes = ZERO; + user.totalAppealingDisputes = ZERO; user.totalDisputes = ZERO; user.totalCoherent = ZERO; user.save(); From 41f261d2ba4793350ab0b0413036754d65935726 Mon Sep 17 00:00:00 2001 From: nhestrompia Date: Fri, 15 Sep 2023 18:54:43 +0300 Subject: [PATCH 21/36] fix: query update based on subgraph changes --- web/src/hooks/queries/useMyCasesCounterQuery.ts | 1 + web/src/pages/Dashboard/index.tsx | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/web/src/hooks/queries/useMyCasesCounterQuery.ts b/web/src/hooks/queries/useMyCasesCounterQuery.ts index df891cf60..c12ba1324 100644 --- a/web/src/hooks/queries/useMyCasesCounterQuery.ts +++ b/web/src/hooks/queries/useMyCasesCounterQuery.ts @@ -9,6 +9,7 @@ export type { MyCasesCounterQuery }; const myCasesCounter = graphql(` query MyCasesCounter($id: ID!, $where: Dispute_filter) { user(id: $id) { + totalAppealingDisputes disputes(orderBy: lastPeriodChange, where: $where) { id } diff --git a/web/src/pages/Dashboard/index.tsx b/web/src/pages/Dashboard/index.tsx index 5eea8c05c..9043a0dc8 100644 --- a/web/src/pages/Dashboard/index.tsx +++ b/web/src/pages/Dashboard/index.tsx @@ -5,7 +5,7 @@ import { DisputeDetailsFragment, useMyCasesQuery } from "queries/useCasesQuery"; import { useFiltersContext } from "context/FilterProvider"; import { useMyCasesCounterQuery } from "queries/useMyCasesCounterQuery"; import { useUserQuery, UserQuery } from "queries/useUser"; -import { OrderDirection, Period } from "src/graphql/graphql"; +import { OrderDirection } from "src/graphql/graphql"; import JurorInfo from "./JurorInfo"; import Courts from "./Courts"; import CasesDisplay from "components/CasesDisplay"; @@ -63,8 +63,8 @@ const Dashboard: React.FC = () => { const disputeSkip = debouncedSearch ? 0 : 3 * (currentPage - 1); const direction = timeFilter === 0 ? OrderDirection.Desc : OrderDirection.Asc; const courtChoice = courtFilter === 0 ? {} : { court: courtFilter.toString() }; - const { data: userAppealCases } = useMyCasesCounterQuery(address, { period: Period.Appeal, ...courtChoice }); - const userAppealCasesNumber = userAppealCases?.user?.disputes.length; + const { data: userAppealCases } = useMyCasesCounterQuery(address, { ...courtChoice }); + const userAppealCasesNumber = userAppealCases?.user?.totalAppealingDisputes; const queryFilters = { ...combinedQueryFilters, ...courtChoice }; const { data: disputesData } = useMyCasesQuery(address, disputeSkip, queryFilters, direction); const { data: userData } = useUserQuery(address); From 7a3ea0766831deddb1d3f65af8f3bc9bb273b70f Mon Sep 17 00:00:00 2001 From: alcercu <333aleix333@gmail.com> Date: Fri, 15 Sep 2023 18:35:21 +0200 Subject: [PATCH 22/36] refactor(web): move totalAppealingDisputes to useUser query --- .../hooks/queries/useMyCasesCounterQuery.ts | 32 ------------------- web/src/hooks/queries/useUser.ts | 1 + web/src/pages/Dashboard/index.tsx | 5 +-- 3 files changed, 2 insertions(+), 36 deletions(-) delete mode 100644 web/src/hooks/queries/useMyCasesCounterQuery.ts diff --git a/web/src/hooks/queries/useMyCasesCounterQuery.ts b/web/src/hooks/queries/useMyCasesCounterQuery.ts deleted file mode 100644 index c12ba1324..000000000 --- a/web/src/hooks/queries/useMyCasesCounterQuery.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { graphql } from "src/graphql"; -import { Address } from "viem"; -import { useQuery } from "@tanstack/react-query"; -import { MyCasesCounterQuery, Dispute_Filter } from "src/graphql/graphql"; -import { graphqlQueryFnHelper } from "utils/graphqlQueryFnHelper"; -import { isUndefined } from "utils/index"; -export type { MyCasesCounterQuery }; - -const myCasesCounter = graphql(` - query MyCasesCounter($id: ID!, $where: Dispute_filter) { - user(id: $id) { - totalAppealingDisputes - disputes(orderBy: lastPeriodChange, where: $where) { - id - } - } - } -`); - -export const useMyCasesCounterQuery = (user?: Address, where?: Dispute_Filter) => { - const isEnabled = !isUndefined(user); - - return useQuery({ - queryKey: [`useMyCasesCounterQuery`, user], - enabled: isEnabled, - queryFn: async () => - await graphqlQueryFnHelper(myCasesCounter, { - id: user?.toLowerCase(), - where, - }), - }); -}; diff --git a/web/src/hooks/queries/useUser.ts b/web/src/hooks/queries/useUser.ts index c49eefab0..10f68859c 100644 --- a/web/src/hooks/queries/useUser.ts +++ b/web/src/hooks/queries/useUser.ts @@ -10,6 +10,7 @@ const userQuery = graphql(` user(id: $address) { totalDisputes totalResolvedDisputes + totalAppealingDisputes totalCoherent tokens { court { diff --git a/web/src/pages/Dashboard/index.tsx b/web/src/pages/Dashboard/index.tsx index 9043a0dc8..506b1956b 100644 --- a/web/src/pages/Dashboard/index.tsx +++ b/web/src/pages/Dashboard/index.tsx @@ -3,7 +3,6 @@ import styled from "styled-components"; import { useAccount } from "wagmi"; import { DisputeDetailsFragment, useMyCasesQuery } from "queries/useCasesQuery"; import { useFiltersContext } from "context/FilterProvider"; -import { useMyCasesCounterQuery } from "queries/useMyCasesCounterQuery"; import { useUserQuery, UserQuery } from "queries/useUser"; import { OrderDirection } from "src/graphql/graphql"; import JurorInfo from "./JurorInfo"; @@ -63,12 +62,10 @@ const Dashboard: React.FC = () => { const disputeSkip = debouncedSearch ? 0 : 3 * (currentPage - 1); const direction = timeFilter === 0 ? OrderDirection.Desc : OrderDirection.Asc; const courtChoice = courtFilter === 0 ? {} : { court: courtFilter.toString() }; - const { data: userAppealCases } = useMyCasesCounterQuery(address, { ...courtChoice }); - const userAppealCasesNumber = userAppealCases?.user?.totalAppealingDisputes; const queryFilters = { ...combinedQueryFilters, ...courtChoice }; const { data: disputesData } = useMyCasesQuery(address, disputeSkip, queryFilters, direction); const { data: userData } = useUserQuery(address); - const totalPages = calculatePages(statusFilter, userData, casesPerPage, userAppealCasesNumber); + const totalPages = calculatePages(statusFilter, userData, casesPerPage, userData?.user?.totalAppealingDisputes); useEffect(() => { setCurrentPage(1); From d9ef8f5039d00cf66adf2939a86336a05f17c100 Mon Sep 17 00:00:00 2001 From: nhestrompia Date: Mon, 18 Sep 2023 13:13:42 +0300 Subject: [PATCH 23/36] fix: dashboard new user problem --- web/src/hooks/queries/useUser.ts | 9 ++++++--- web/src/pages/Dashboard/index.tsx | 30 +++++++++++++++++++++++------- 2 files changed, 29 insertions(+), 10 deletions(-) diff --git a/web/src/hooks/queries/useUser.ts b/web/src/hooks/queries/useUser.ts index 10f68859c..38baaedeb 100644 --- a/web/src/hooks/queries/useUser.ts +++ b/web/src/hooks/queries/useUser.ts @@ -1,12 +1,12 @@ import { useQuery } from "@tanstack/react-query"; import { Address } from "viem"; import { graphql } from "src/graphql"; -import { UserQuery } from "src/graphql/graphql"; +import { UserQuery, Dispute_Filter } from "src/graphql/graphql"; import { graphqlQueryFnHelper } from "utils/graphqlQueryFnHelper"; export type { UserQuery }; const userQuery = graphql(` - query User($address: ID!) { + query User($address: ID!, $where: Dispute_filter) { user(id: $address) { totalDisputes totalResolvedDisputes @@ -22,11 +22,14 @@ const userQuery = graphql(` pnkAmount ethAmount } + disputes(orderBy: lastPeriodChange, where: $where) { + id + } } } `); -export const useUserQuery = (address?: Address) => { +export const useUserQuery = (address?: Address, where?: Dispute_Filter) => { const isEnabled = address !== undefined; return useQuery({ diff --git a/web/src/pages/Dashboard/index.tsx b/web/src/pages/Dashboard/index.tsx index 506b1956b..b4c9c447d 100644 --- a/web/src/pages/Dashboard/index.tsx +++ b/web/src/pages/Dashboard/index.tsx @@ -29,25 +29,35 @@ const ConnectWalletContainer = styled.div` color: ${({ theme }) => theme.primaryText}; `; -const calculatePages = (status: number, data: UserQuery | undefined, casesPerPage: number, myAppeals?: number) => { - if (!data) { +const calculatePages = ( + status: number, + data: UserQuery | undefined, + casesPerPage: number, + courtFilter: number, + myAppeals?: number +) => { + if (!data?.user) { return 0; } let totalCases = 0; + if (courtFilter !== 0) { + return data?.user?.disputes?.length / casesPerPage; + } + switch (status) { case 1: - totalCases = data.user?.totalDisputes - data.user?.totalResolvedDisputes; + totalCases = parseInt(data.user?.totalDisputes) - parseInt(data.user?.totalResolvedDisputes); break; case 2: - totalCases = data.user?.totalResolvedDisputes ?? 0; + totalCases = parseInt(data.user?.totalResolvedDisputes) ?? 0; break; case 3: totalCases = myAppeals ?? 0; break; default: - totalCases = data.user?.totalDisputes ?? 0; + totalCases = parseInt(data.user?.totalDisputes) ?? 0; } return totalCases / casesPerPage; @@ -64,8 +74,14 @@ const Dashboard: React.FC = () => { const courtChoice = courtFilter === 0 ? {} : { court: courtFilter.toString() }; const queryFilters = { ...combinedQueryFilters, ...courtChoice }; const { data: disputesData } = useMyCasesQuery(address, disputeSkip, queryFilters, direction); - const { data: userData } = useUserQuery(address); - const totalPages = calculatePages(statusFilter, userData, casesPerPage, userData?.user?.totalAppealingDisputes); + const { data: userData } = useUserQuery(address, { ...courtChoice }); + const totalPages = calculatePages( + statusFilter, + userData, + casesPerPage, + courtFilter, + parseInt(userData?.user?.totalAppealingDisputes) + ); useEffect(() => { setCurrentPage(1); From 56cb87ae6bbb14240d4aaeb0f17f725b36dfc4e2 Mon Sep 17 00:00:00 2001 From: alcercu <333aleix333@gmail.com> Date: Mon, 18 Sep 2023 18:05:54 +0200 Subject: [PATCH 24/36] feat(subgraph): add periodDeadline field to dispute entity --- subgraph/schema.graphql | 1 + subgraph/src/KlerosCore.ts | 9 +++++++-- subgraph/src/entities/Dispute.ts | 8 ++++++-- 3 files changed, 14 insertions(+), 4 deletions(-) diff --git a/subgraph/schema.graphql b/subgraph/schema.graphql index 2b58ea2bf..b904baccc 100644 --- a/subgraph/schema.graphql +++ b/subgraph/schema.graphql @@ -151,6 +151,7 @@ type Dispute @entity { overridden: Boolean! lastPeriodChange: BigInt! lastPeriodChangeBlockNumber: BigInt! + periodDeadline: BigInt! rounds: [Round!]! @derivedFrom(field: "dispute") currentRound: Round! currentRoundIndex: BigInt! diff --git a/subgraph/src/KlerosCore.ts b/subgraph/src/KlerosCore.ts index ca29acabf..738b39043 100644 --- a/subgraph/src/KlerosCore.ts +++ b/subgraph/src/KlerosCore.ts @@ -15,7 +15,7 @@ import { AcceptedFeeToken, } from "../generated/KlerosCore/KlerosCore"; import { ZERO, ONE } from "./utils"; -import { createCourtFromEvent, getFeeForJuror } from "./entities/Court"; +import { createCourtFromEvent } from "./entities/Court"; import { createDisputeKitFromEvent, filterSupportedDisputeKits } from "./entities/DisputeKit"; import { createDisputeFromEvent } from "./entities/Dispute"; import { createRoundFromRoundInfo } from "./entities/Round"; @@ -25,7 +25,7 @@ import { updateJurorDelayedStake, updateJurorStake } from "./entities/JurorToken import { createDrawFromEvent } from "./entities/Draw"; import { updateTokenAndEthShiftFromEvent } from "./entities/TokenAndEthShift"; import { updateArbitrableCases } from "./entities/Arbitrable"; -import { Court, Dispute, FeeToken, User } from "../generated/schema"; +import { Court, Dispute, User } from "../generated/schema"; import { BigInt } from "@graphprotocol/graph-ts"; import { updatePenalty } from "./entities/Penalty"; import { ensureFeeToken } from "./entities/FeeToken"; @@ -128,6 +128,11 @@ export function handleNewPeriod(event: NewPeriod): void { dispute.period = newPeriod; dispute.lastPeriodChange = event.block.timestamp; dispute.lastPeriodChangeBlockNumber = event.block.number; + if (newPeriod !== "execution") { + dispute.periodDeadline = event.block.timestamp.plus(court.timesPerPeriod[event.params._period]); + } else { + dispute.periodDeadline = BigInt.fromU64(U64.MAX_VALUE); + } dispute.save(); court.save(); } diff --git a/subgraph/src/entities/Dispute.ts b/subgraph/src/entities/Dispute.ts index 22936d038..5fac58f69 100644 --- a/subgraph/src/entities/Dispute.ts +++ b/subgraph/src/entities/Dispute.ts @@ -1,5 +1,5 @@ import { KlerosCore, DisputeCreation } from "../../generated/KlerosCore/KlerosCore"; -import { Dispute } from "../../generated/schema"; +import { Court, Dispute } from "../../generated/schema"; import { ZERO } from "../utils"; export function createDisputeFromEvent(event: DisputeCreation): void { @@ -7,7 +7,8 @@ export function createDisputeFromEvent(event: DisputeCreation): void { const disputeID = event.params._disputeID; const disputeContractState = contract.disputes(disputeID); const dispute = new Dispute(disputeID.toString()); - dispute.court = disputeContractState.value0.toString(); + const courtID = disputeContractState.value0.toString(); + dispute.court = courtID; dispute.arbitrated = event.params._arbitrable.toHexString(); dispute.period = "evidence"; dispute.ruled = false; @@ -16,6 +17,9 @@ export function createDisputeFromEvent(event: DisputeCreation): void { dispute.overridden = false; dispute.lastPeriodChange = event.block.timestamp; dispute.lastPeriodChangeBlockNumber = event.block.number; + const court = Court.load(courtID); + if (!court) return; + dispute.periodDeadline = event.block.timestamp.plus(court.timesPerPeriod[0]); dispute.currentRoundIndex = ZERO; const roundID = `${disputeID.toString()}-${ZERO.toString()}`; dispute.currentRound = roundID; From 4ba1cb1c249f724ae154b65efc37149deeff884e Mon Sep 17 00:00:00 2001 From: alcercu <333aleix333@gmail.com> Date: Mon, 18 Sep 2023 18:25:50 +0200 Subject: [PATCH 25/36] chore(web): use devnet subgraph --- web/.env.devnet.public | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/web/.env.devnet.public b/web/.env.devnet.public index 727078abd..6b5dbedb8 100644 --- a/web/.env.devnet.public +++ b/web/.env.devnet.public @@ -1,4 +1,4 @@ # Do not enter sensitive information here. export REACT_APP_DEPLOYMENT=devnet -export REACT_APP_KLEROS_CORE_SUBGRAPH_DEVNET=https://api.thegraph.com/subgraphs/name/alcercu/kleroscoretest -export REACT_APP_DISPUTE_TEMPLATE_ARBGOERLI_SUBGRAPH_DEVNET=https://api.thegraph.com/subgraphs/name/alcercu/templateregistrydevnet \ No newline at end of file +export REACT_APP_KLEROS_CORE_SUBGRAPH_DEVNET=https://api.thegraph.com/subgraphs/name/alcercu/kleroscoredev +export REACT_APP_DISPUTE_TEMPLATE_ARBGOERLI_SUBGRAPH_DEVNET=https://api.thegraph.com/subgraphs/name/alcercu/templateregistrydevnet From 5740f02d142b4bdf86e340ab766bfa63291ccc20 Mon Sep 17 00:00:00 2001 From: nhestrompia Date: Tue, 19 Sep 2023 13:31:37 +0300 Subject: [PATCH 26/36] fix: dashboard pagination --- web/src/context/FilterProvider.tsx | 2 +- web/src/hooks/queries/useUser.ts | 10 ++++++---- web/src/pages/Dashboard/index.tsx | 11 ++++++++--- 3 files changed, 15 insertions(+), 8 deletions(-) diff --git a/web/src/context/FilterProvider.tsx b/web/src/context/FilterProvider.tsx index 7f9c7ddda..8c0225eb2 100644 --- a/web/src/context/FilterProvider.tsx +++ b/web/src/context/FilterProvider.tsx @@ -31,7 +31,7 @@ const Context = createContext({ combinedQueryFilters: {}, }); -const getStatusPeriod = (statusFilter: number) => { +export const getStatusPeriod = (statusFilter: number) => { switch (statusFilter) { case 1: return { ruled: false }; diff --git a/web/src/hooks/queries/useUser.ts b/web/src/hooks/queries/useUser.ts index 38baaedeb..325a54034 100644 --- a/web/src/hooks/queries/useUser.ts +++ b/web/src/hooks/queries/useUser.ts @@ -8,6 +8,9 @@ export type { UserQuery }; const userQuery = graphql(` query User($address: ID!, $where: Dispute_filter) { user(id: $address) { + disputes(orderBy: lastPeriodChange, where: $where) { + id + } totalDisputes totalResolvedDisputes totalAppealingDisputes @@ -22,9 +25,6 @@ const userQuery = graphql(` pnkAmount ethAmount } - disputes(orderBy: lastPeriodChange, where: $where) { - id - } } } `); @@ -32,9 +32,11 @@ const userQuery = graphql(` export const useUserQuery = (address?: Address, where?: Dispute_Filter) => { const isEnabled = address !== undefined; + console.log("where user", where); + return useQuery({ queryKey: [`userQuery${address?.toLowerCase()}`], enabled: isEnabled, - queryFn: async () => await graphqlQueryFnHelper(userQuery, { address: address?.toLowerCase() }), + queryFn: async () => await graphqlQueryFnHelper(userQuery, { address: address?.toLowerCase(), where }), }); }; diff --git a/web/src/pages/Dashboard/index.tsx b/web/src/pages/Dashboard/index.tsx index b4c9c447d..a52e1be19 100644 --- a/web/src/pages/Dashboard/index.tsx +++ b/web/src/pages/Dashboard/index.tsx @@ -2,7 +2,7 @@ import React, { useState, useEffect } from "react"; import styled from "styled-components"; import { useAccount } from "wagmi"; import { DisputeDetailsFragment, useMyCasesQuery } from "queries/useCasesQuery"; -import { useFiltersContext } from "context/FilterProvider"; +import { getStatusPeriod, useFiltersContext } from "context/FilterProvider"; import { useUserQuery, UserQuery } from "queries/useUser"; import { OrderDirection } from "src/graphql/graphql"; import JurorInfo from "./JurorInfo"; @@ -42,10 +42,11 @@ const calculatePages = ( let totalCases = 0; + console.log("court", courtFilter, data); + if (courtFilter !== 0) { return data?.user?.disputes?.length / casesPerPage; } - switch (status) { case 1: totalCases = parseInt(data.user?.totalDisputes) - parseInt(data.user?.totalResolvedDisputes); @@ -72,9 +73,11 @@ const Dashboard: React.FC = () => { const disputeSkip = debouncedSearch ? 0 : 3 * (currentPage - 1); const direction = timeFilter === 0 ? OrderDirection.Desc : OrderDirection.Asc; const courtChoice = courtFilter === 0 ? {} : { court: courtFilter.toString() }; + const statusChoice = getStatusPeriod(statusFilter); const queryFilters = { ...combinedQueryFilters, ...courtChoice }; const { data: disputesData } = useMyCasesQuery(address, disputeSkip, queryFilters, direction); - const { data: userData } = useUserQuery(address, { ...courtChoice }); + console.log("🚀 ~ file: index.tsx:88 ~ totalPages:", { ...courtChoice, ...statusChoice }); + const { data: userData } = useUserQuery(address, { ...courtChoice, ...statusChoice }); const totalPages = calculatePages( statusFilter, userData, @@ -83,6 +86,8 @@ const Dashboard: React.FC = () => { parseInt(userData?.user?.totalAppealingDisputes) ); + console.log("pages", totalPages); + useEffect(() => { setCurrentPage(1); }, [statusFilter, courtFilter]); From 36608f3d2cce6982a9a154317197bbd01f8a4b3c Mon Sep 17 00:00:00 2001 From: alcercu <333aleix333@gmail.com> Date: Wed, 4 Oct 2023 17:49:20 +0200 Subject: [PATCH 27/36] feat(web): cases filtering state tracked with url --- web/src/app.tsx | 28 +++--- web/src/components/CasesDisplay/Filters.tsx | 35 +++++--- web/src/components/CasesDisplay/Search.tsx | 79 ++++++++--------- web/src/components/CasesDisplay/index.tsx | 4 +- web/src/hooks/queries/useCounter.ts | 1 + web/src/hooks/queries/useCourtDetails.ts | 2 +- web/src/hooks/queries/useCourtTree.ts | 12 +++ web/src/hooks/queries/useUser.ts | 59 ++++++++----- web/src/layout/Header/navbar/Explore.tsx | 4 +- web/src/pages/Cases/CasesFetcher.tsx | 75 ++++++++++++++++ web/src/pages/Cases/index.tsx | 92 +++----------------- web/src/pages/Courts/TopSearch.tsx | 22 ++--- web/src/pages/Dashboard/index.tsx | 94 ++++++--------------- web/src/utils/uri.ts | 23 +++++ 14 files changed, 266 insertions(+), 264 deletions(-) create mode 100644 web/src/pages/Cases/CasesFetcher.tsx create mode 100644 web/src/utils/uri.ts diff --git a/web/src/app.tsx b/web/src/app.tsx index 76e04424a..f0f11af3e 100644 --- a/web/src/app.tsx +++ b/web/src/app.tsx @@ -1,12 +1,11 @@ import React from "react"; -import { Route, useLocation } from "react-router-dom"; +import { Route } from "react-router-dom"; import { SentryRoutes } from "./utils/sentry"; import "react-loading-skeleton/dist/skeleton.css"; import "react-toastify/dist/ReactToastify.css"; import Web3Provider from "context/Web3Provider"; import QueryClientProvider from "context/QueryClientProvider"; import StyledComponentsProvider from "context/StyledComponentsProvider"; -import { FilterProvider } from "context/FilterProvider"; import RefetchOnBlock from "context/RefetchOnBlock"; import Layout from "layout/index"; import Home from "./pages/Home"; @@ -16,26 +15,21 @@ import Courts from "./pages/Courts"; import DisputeTemplateView from "./pages/DisputeTemplateView"; const App: React.FC = () => { - const location = useLocation(); - - const routeKey = location.pathname; return ( - - - }> - } /> - } /> - } /> - } /> - } /> - Justice not found here ¯\_( ͡° ͜ʖ ͡°)_/¯} /> - - - + + }> + } /> + } /> + } /> + } /> + } /> + Justice not found here ¯\_( ͡° ͜ʖ ͡°)_/¯} /> + + diff --git a/web/src/components/CasesDisplay/Filters.tsx b/web/src/components/CasesDisplay/Filters.tsx index c37bda28c..6919b6543 100644 --- a/web/src/components/CasesDisplay/Filters.tsx +++ b/web/src/components/CasesDisplay/Filters.tsx @@ -1,7 +1,8 @@ import React from "react"; import styled, { useTheme } from "styled-components"; +import { useNavigate, useParams } from "react-router-dom"; import { DropdownSelect } from "@kleros/ui-components-library"; -import { useFiltersContext } from "~src/context/FilterProvider"; +import { decodeURIFilter, encodeURIFilter, useRootPath } from "utils/uri"; const Container = styled.div` display: flex; @@ -12,10 +13,20 @@ const Container = styled.div` const Filters: React.FC = () => { const theme = useTheme(); - const { setTimeFilter, setStatusFilter } = useFiltersContext(); + const { order, filter } = useParams(); + const { ruled, period, ...filterObject } = decodeURIFilter(filter ?? "all"); + const navigate = useNavigate(); + const location = useRootPath(); const handleStatusChange = (value: string | number) => { - setStatusFilter(Number(value)); + const parsedValue = JSON.parse(value as string); + const encodedFilter = encodeURIFilter({ ...filterObject, ...parsedValue }); + navigate(`${location}/1/${order}/${encodedFilter}`); + }; + + const handleOrderChange = (value: string | number) => { + const encodedFilter = encodeURIFilter({ ruled, period, ...filterObject }); + navigate(`${location}/1/${value}/${encodedFilter}`); }; return ( @@ -24,23 +35,23 @@ const Filters: React.FC = () => { smallButton simpleButton items={[ - { value: 0, text: "All Cases", dot: theme.primaryText }, - { value: 1, text: "In Progress", dot: theme.primaryBlue }, - { value: 2, text: "Closed", dot: theme.primaryPurple }, - { value: 3, text: "Appeal", dot: theme.tint }, + { value: JSON.stringify({}), text: "All Cases", dot: theme.primaryText }, + { value: JSON.stringify({ ruled: false }), text: "In Progress", dot: theme.primaryBlue }, + { value: JSON.stringify({ ruled: true }), text: "Closed", dot: theme.primaryPurple }, + { value: JSON.stringify({ period: "appeal" }), text: "Appeal", dot: theme.tint }, ]} - defaultValue={0} + defaultValue={JSON.stringify({ ruled, period })} callback={handleStatusChange} /> ); diff --git a/web/src/components/CasesDisplay/Search.tsx b/web/src/components/CasesDisplay/Search.tsx index b697b0414..cecda95e8 100644 --- a/web/src/components/CasesDisplay/Search.tsx +++ b/web/src/components/CasesDisplay/Search.tsx @@ -1,7 +1,12 @@ -import React from "react"; +import React, { useMemo, useState } from "react"; +import { useNavigate, useParams } from "react-router-dom"; +import { useDebounce } from "react-use"; import styled from "styled-components"; +import Skeleton from "react-loading-skeleton"; import { Searchbar, DropdownCascader } from "@kleros/ui-components-library"; -import { useFiltersContext } from "context/FilterProvider"; +import { rootCourtToItems, useCourtTree } from "hooks/queries/useCourtTree"; +import { isUndefined } from "utils/index"; +import { decodeURIFilter, encodeURIFilter, useRootPath } from "utils/uri"; const Container = styled.div` display: flex; @@ -22,12 +27,27 @@ const StyledSearchbar = styled(Searchbar)` } `; -interface ISearch { - setCourtFilter: (arg0: number) => void; -} - -const Search: React.FC = ({ setCourtFilter }) => { - const { search, setSearch } = useFiltersContext(); +const Search: React.FC = () => { + const { page, order, filter } = useParams(); + const location = useRootPath(); + const decodedFilter = decodeURIFilter(filter ?? "all"); + const { id: searchValue, ...filterObject } = decodedFilter; + const [search, setSearch] = useState(searchValue ?? ""); + const navigate = useNavigate(); + useDebounce( + () => { + const newFilters = search === "" ? { ...filterObject } : { ...filterObject, id: search }; + const encodedFilter = encodeURIFilter(newFilters); + navigate(`${location}/${page}/${order}/${encodedFilter}`); + }, + 500, + [search] + ); + const { data: courtTreeData } = useCourtTree(); + const items = useMemo( + () => !isUndefined(courtTreeData) && [rootCourtToItems(courtTreeData.court, "id")], + [courtTreeData] + ); return (
@@ -39,40 +59,15 @@ const Search: React.FC = ({ setCourtFilter }) => { onChange={(e) => setSearch(e.target.value)} /> - + {items ? ( + navigate(`${location}/${page}/${order}/${{ ...filterObject, court: value }}`)} + /> + ) : ( + + )}
); }; diff --git a/web/src/components/CasesDisplay/index.tsx b/web/src/components/CasesDisplay/index.tsx index ae81409dc..9d5aa9b22 100644 --- a/web/src/components/CasesDisplay/index.tsx +++ b/web/src/components/CasesDisplay/index.tsx @@ -13,7 +13,6 @@ interface ICasesDisplay extends ICasesGrid { numberClosedDisputes?: number; title?: string; className?: string; - setCourtFilter: (arg0: number) => void; } const CasesDisplay: React.FC = ({ @@ -26,12 +25,11 @@ const CasesDisplay: React.FC = ({ title = "Cases", className, totalPages, - setCourtFilter, }) => { return (

{title}

- + diff --git a/web/src/hooks/queries/useCounter.ts b/web/src/hooks/queries/useCounter.ts index e771d7c31..2a71c3834 100644 --- a/web/src/hooks/queries/useCounter.ts +++ b/web/src/hooks/queries/useCounter.ts @@ -2,6 +2,7 @@ import { graphql } from "src/graphql"; import { useQuery } from "@tanstack/react-query"; import { CounterQuery } from "src/graphql/graphql"; import { graphqlQueryFnHelper } from "utils/graphqlQueryFnHelper"; +export type { CounterQuery }; const counterQuery = graphql(` query Counter { diff --git a/web/src/hooks/queries/useCourtDetails.ts b/web/src/hooks/queries/useCourtDetails.ts index 90a820842..14c708e34 100644 --- a/web/src/hooks/queries/useCourtDetails.ts +++ b/web/src/hooks/queries/useCourtDetails.ts @@ -24,7 +24,7 @@ const courtDetailsQuery = graphql(` export const useCourtDetails = (id?: string) => { const isEnabled = id !== undefined; - return useQuery({ + return useQuery({ queryKey: ["refetchOnBlock", `courtDetails${id}`], enabled: isEnabled, queryFn: async () => await graphqlQueryFnHelper(courtDetailsQuery, { id }), diff --git a/web/src/hooks/queries/useCourtTree.ts b/web/src/hooks/queries/useCourtTree.ts index a5b4cf947..532aa5acf 100644 --- a/web/src/hooks/queries/useCourtTree.ts +++ b/web/src/hooks/queries/useCourtTree.ts @@ -39,3 +39,15 @@ export const useCourtTree = () => { queryFn: async () => await graphqlQueryFnHelper(courtTreeQuery, {}), }); }; + +interface IItem { + label: string; + value: string; + children?: IItem[]; +} + +export const rootCourtToItems = (court: CourtTreeQuery["court"], value?: "id" | "path"): IItem => ({ + label: court!.name ? court!.name : "Unnamed Court", + value: value === "id" ? court!.id : `/courts/${court!.id}`, + children: court!.children.length > 0 ? court!.children.map((child) => rootCourtToItems(child)) : undefined, +}); diff --git a/web/src/hooks/queries/useUser.ts b/web/src/hooks/queries/useUser.ts index 325a54034..94aa77828 100644 --- a/web/src/hooks/queries/useUser.ts +++ b/web/src/hooks/queries/useUser.ts @@ -1,42 +1,57 @@ import { useQuery } from "@tanstack/react-query"; import { Address } from "viem"; import { graphql } from "src/graphql"; -import { UserQuery, Dispute_Filter } from "src/graphql/graphql"; +import { UserQuery, Dispute_Filter, UserDisputeFilterQuery, UserDetailsFragment } from "src/graphql/graphql"; import { graphqlQueryFnHelper } from "utils/graphqlQueryFnHelper"; -export type { UserQuery }; +export type { UserQuery, UserDetailsFragment }; + +export const userFragment = graphql(` + fragment UserDetails on User { + totalDisputes + totalResolvedDisputes + totalAppealingDisputes + totalCoherent + tokens { + court { + id + name + } + } + shifts { + pnkAmount + ethAmount + } + } +`); const userQuery = graphql(` - query User($address: ID!, $where: Dispute_filter) { + query User($address: ID!) { user(id: $address) { - disputes(orderBy: lastPeriodChange, where: $where) { + disputes(orderBy: lastPeriodChange) { id } - totalDisputes - totalResolvedDisputes - totalAppealingDisputes - totalCoherent - tokens { - court { - id - name - } - } - shifts { - pnkAmount - ethAmount + ...UserDetails + } + } +`); + +const userQueryDisputeFilter = graphql(` + query UserDisputeFilter($address: ID!, $where: Dispute_filter) { + user(id: $address) { + disputes(orderBy: lastPeriodChange, where: $where) { + id } + ...UserDetails } } `); export const useUserQuery = (address?: Address, where?: Dispute_Filter) => { const isEnabled = address !== undefined; - - console.log("where user", where); - - return useQuery({ + const query = where ? userQueryDisputeFilter : userQuery; + return useQuery({ queryKey: [`userQuery${address?.toLowerCase()}`], enabled: isEnabled, - queryFn: async () => await graphqlQueryFnHelper(userQuery, { address: address?.toLowerCase(), where }), + queryFn: async () => await graphqlQueryFnHelper(query, { address: address?.toLowerCase(), where }), }); }; diff --git a/web/src/layout/Header/navbar/Explore.tsx b/web/src/layout/Header/navbar/Explore.tsx index 87cf63d12..776c2a56a 100644 --- a/web/src/layout/Header/navbar/Explore.tsx +++ b/web/src/layout/Header/navbar/Explore.tsx @@ -18,9 +18,9 @@ const LinkContainer = styled.div` `; const links = [ - { to: "/cases", text: "Cases" }, + { to: "/cases/display/1/desc/all", text: "Cases" }, { to: "/courts", text: "Courts" }, - { to: "/dashboard", text: "Dashboard" }, + { to: "/dashboard/1/desc/all", text: "Dashboard" }, ]; const Explore: React.FC = () => { diff --git a/web/src/pages/Cases/CasesFetcher.tsx b/web/src/pages/Cases/CasesFetcher.tsx new file mode 100644 index 000000000..8e69ef672 --- /dev/null +++ b/web/src/pages/Cases/CasesFetcher.tsx @@ -0,0 +1,75 @@ +import React from "react"; +import { useParams, useNavigate } from "react-router-dom"; +import { DisputeDetailsFragment, Dispute_Filter, OrderDirection } from "src/graphql/graphql"; +import CasesDisplay from "components/CasesDisplay"; +import { useCasesQuery } from "queries/useCasesQuery"; +import { useCounterQuery, CounterQuery } from "queries/useCounter"; +import { useCourtDetails, CourtDetailsQuery } from "queries/useCourtDetails"; +import { decodeURIFilter, useRootPath } from "utils/uri"; +import { isUndefined } from "utils/index"; + +const calculateStats = ( + isCourtFilter: boolean, + courtData: CourtDetailsQuery["court"], + counters: CounterQuery["counter"], + filter?: Dispute_Filter +): { totalCases: number; ruledCases: number } => { + let totalCases: number, ruledCases: number; + if (filter?.period === "appeal") { + totalCases = isCourtFilter ? courtData?.numberAppealingDisputes : counters?.casesAppealing; + ruledCases = 0; + } else if (isUndefined(filter?.ruled)) { + totalCases = isCourtFilter ? courtData?.numberDisputes : counters?.cases; + ruledCases = isCourtFilter ? courtData?.numberClosedDisputes : counters?.casesRuled; + } else if (filter?.ruled) { + totalCases = isCourtFilter ? courtData?.numberClosedDisputes : counters?.casesAppealing; + ruledCases = totalCases; + } else { + totalCases = isCourtFilter + ? courtData?.numberDisputes - courtData?.numberClosedDisputes + : counters?.cases - counters?.casesRuled; + ruledCases = 0; + } + return { + totalCases: isNaN(totalCases) ? 0 : totalCases, + ruledCases: isNaN(ruledCases) ? 0 : ruledCases, + }; +}; + +const CasesFetcher: React.FC = () => { + const { page, order, filter } = useParams(); + const location = useRootPath(); + const navigate = useNavigate(); + const casesPerPage = 3; + const pageNumber = parseInt(page ?? "1"); + const disputeSkip = casesPerPage * (pageNumber - 1); + const { data: counterData } = useCounterQuery(); + const decodedFilter = decodeURIFilter(filter ?? "all"); + const isCourtFilter = !isUndefined(decodedFilter?.court); + const { data: courtData } = useCourtDetails(decodedFilter?.court?.toString()); + const { data } = useCasesQuery( + disputeSkip, + decodedFilter, + order === "asc" ? OrderDirection.Asc : OrderDirection.Desc + ); + const { totalCases, ruledCases } = calculateStats( + isCourtFilter, + courtData?.court, + counterData?.counter, + decodedFilter + ); + + return ( + navigate(`${location}/${newPage}/${order}/${filter}`)} + totalPages={!isUndefined(totalCases) ? Math.ceil(totalCases / casesPerPage) : 1} + {...{ casesPerPage }} + /> + ); +}; + +export default CasesFetcher; diff --git a/web/src/pages/Cases/index.tsx b/web/src/pages/Cases/index.tsx index 9ebf43fc0..2f5540874 100644 --- a/web/src/pages/Cases/index.tsx +++ b/web/src/pages/Cases/index.tsx @@ -1,13 +1,8 @@ -import React, { useState, useEffect } from "react"; +import React from "react"; import styled from "styled-components"; import { Routes, Route } from "react-router-dom"; -import { useFiltersContext } from "context/FilterProvider"; -import { DisputeDetailsFragment, useCasesQuery } from "queries/useCasesQuery"; -import { useCourtDetails, CourtDetailsQuery } from "queries/useCourtDetails"; -import { useCounterQuery } from "queries/useCounter"; -import { CounterQuery, OrderDirection } from "src/graphql/graphql"; -import CasesDisplay from "components/CasesDisplay"; import CaseDetails from "./CaseDetails"; +import CasesFetcher from "./CasesFetcher"; const Container = styled.div` width: 100%; @@ -16,80 +11,13 @@ const Container = styled.div` padding: 32px; `; -export const calculatePages = ( - status: number, - data: CounterQuery | CourtDetailsQuery | undefined, - casesPerPage: number, - numberDisputes: number -) => { - if (!data) { - return 0; - } - - let totalCases = 0; - - switch (status) { - case 1: - totalCases = - "counter" in data - ? data?.counter?.cases - data?.counter?.casesRuled - : (data as CourtDetailsQuery).court?.numberDisputes - (data as CourtDetailsQuery).court?.numberClosedDisputes; - break; - case 2: - totalCases = - "counter" in data ? data?.counter?.casesRuled : (data as CourtDetailsQuery).court?.numberClosedDisputes; - break; - case 3: - totalCases = - "counter" in data ? data?.counter?.casesAppealing : (data as CourtDetailsQuery).court?.numberAppealingDisputes; - break; - default: - totalCases = "counter" in data ? numberDisputes ?? 0 : (data as CourtDetailsQuery).court?.numberDisputes; - } - - return totalCases / casesPerPage; -}; - -const Cases: React.FC = () => { - const casesPerPage = 3; - const { combinedQueryFilters, debouncedSearch, timeFilter, statusFilter } = useFiltersContext(); - const [currentPage, setCurrentPage] = useState(1); - const [courtFilter, setCourtFilter] = useState(0); - const { data: counterData } = useCounterQuery(); - const { data: courtData } = useCourtDetails(courtFilter.toString()); - const direction = timeFilter === 0 ? OrderDirection.Desc : OrderDirection.Asc; - const courtChoice = courtFilter === 0 ? {} : { court: courtFilter.toString() }; - const disputeSkip = debouncedSearch ? 0 : 3 * (currentPage - 1); - const queryFilters = { ...combinedQueryFilters, ...courtChoice }; - const { data } = useCasesQuery(disputeSkip, queryFilters, direction); - const totalPages = - courtFilter !== 0 - ? calculatePages(statusFilter, courtData, casesPerPage, courtData?.court?.numberDisputes) - : calculatePages(statusFilter, counterData, casesPerPage, counterData?.counter?.cases); - - useEffect(() => { - setCurrentPage(1); - }, [statusFilter, courtFilter]); - - return ( - - - - } - /> - } /> - - - ); -}; +const Cases: React.FC = () => ( + + + } /> + } /> + + +); export default Cases; diff --git a/web/src/pages/Courts/TopSearch.tsx b/web/src/pages/Courts/TopSearch.tsx index 7a86a9f9a..69552b797 100644 --- a/web/src/pages/Courts/TopSearch.tsx +++ b/web/src/pages/Courts/TopSearch.tsx @@ -3,29 +3,21 @@ import { useNavigate } from "react-router-dom"; import { DropdownCascader } from "@kleros/ui-components-library"; import { StyledSkeleton } from "components/StyledSkeleton"; import { isUndefined } from "utils/index"; -import { useCourtTree, CourtTreeQuery } from "queries/useCourtTree"; +import { useCourtTree, rootCourtToItems } from "queries/useCourtTree"; const TopSearch: React.FC = () => { const { data } = useCourtTree(); const navigate = useNavigate(); - const items = useMemo(() => !isUndefined(data) && [rootToItems(data.court)], [data]); + const items = useMemo(() => !isUndefined(data) && [rootCourtToItems(data.court)], [data]); return items ? ( - navigate(path)} placeholder="Select Court" /> + navigate(path.toString())} + placeholder="Select Court" + /> ) : ( ); }; -interface IItem { - label: string; - value: string; - children?: IItem[]; -} - -const rootToItems = (court: CourtTreeQuery["court"]): IItem => ({ - label: court!.name ? court!.name : "Unnamed Court", - value: `/courts/${court!.id}`, - children: court!.children.length > 0 ? court!.children.map((child) => rootToItems(child)) : undefined, -}); - export default TopSearch; diff --git a/web/src/pages/Dashboard/index.tsx b/web/src/pages/Dashboard/index.tsx index a52e1be19..582929f9c 100644 --- a/web/src/pages/Dashboard/index.tsx +++ b/web/src/pages/Dashboard/index.tsx @@ -1,14 +1,15 @@ -import React, { useState, useEffect } from "react"; +import React from "react"; import styled from "styled-components"; +import { useNavigate, useParams } from "react-router-dom"; import { useAccount } from "wagmi"; -import { DisputeDetailsFragment, useMyCasesQuery } from "queries/useCasesQuery"; -import { getStatusPeriod, useFiltersContext } from "context/FilterProvider"; -import { useUserQuery, UserQuery } from "queries/useUser"; import { OrderDirection } from "src/graphql/graphql"; -import JurorInfo from "./JurorInfo"; -import Courts from "./Courts"; +import { DisputeDetailsFragment, useMyCasesQuery } from "queries/useCasesQuery"; +import { useUserQuery } from "queries/useUser"; +import { decodeURIFilter, useRootPath } from "utils/uri"; import CasesDisplay from "components/CasesDisplay"; import ConnectWallet from "components/ConnectWallet"; +import JurorInfo from "./JurorInfo"; +import Courts from "./Courts"; const Container = styled.div` width: 100%; @@ -29,68 +30,23 @@ const ConnectWalletContainer = styled.div` color: ${({ theme }) => theme.primaryText}; `; -const calculatePages = ( - status: number, - data: UserQuery | undefined, - casesPerPage: number, - courtFilter: number, - myAppeals?: number -) => { - if (!data?.user) { - return 0; - } - - let totalCases = 0; - - console.log("court", courtFilter, data); - - if (courtFilter !== 0) { - return data?.user?.disputes?.length / casesPerPage; - } - switch (status) { - case 1: - totalCases = parseInt(data.user?.totalDisputes) - parseInt(data.user?.totalResolvedDisputes); - break; - case 2: - totalCases = parseInt(data.user?.totalResolvedDisputes) ?? 0; - break; - case 3: - totalCases = myAppeals ?? 0; - break; - default: - totalCases = parseInt(data.user?.totalDisputes) ?? 0; - } - - return totalCases / casesPerPage; -}; - const Dashboard: React.FC = () => { const { isConnected, address } = useAccount(); - const { combinedQueryFilters, debouncedSearch, timeFilter, statusFilter } = useFiltersContext(); - const [currentPage, setCurrentPage] = useState(1); - const [courtFilter, setCourtFilter] = useState(0); + const { page, order, filter } = useParams(); + const location = useRootPath(); + const navigate = useNavigate(); const casesPerPage = 3; - const disputeSkip = debouncedSearch ? 0 : 3 * (currentPage - 1); - const direction = timeFilter === 0 ? OrderDirection.Desc : OrderDirection.Asc; - const courtChoice = courtFilter === 0 ? {} : { court: courtFilter.toString() }; - const statusChoice = getStatusPeriod(statusFilter); - const queryFilters = { ...combinedQueryFilters, ...courtChoice }; - const { data: disputesData } = useMyCasesQuery(address, disputeSkip, queryFilters, direction); - console.log("🚀 ~ file: index.tsx:88 ~ totalPages:", { ...courtChoice, ...statusChoice }); - const { data: userData } = useUserQuery(address, { ...courtChoice, ...statusChoice }); - const totalPages = calculatePages( - statusFilter, - userData, - casesPerPage, - courtFilter, - parseInt(userData?.user?.totalAppealingDisputes) + const pageNumber = parseInt(page ?? "1"); + const disputeSkip = casesPerPage * (pageNumber - 1); + const decodedFilter = decodeURIFilter(filter ?? "all"); + const { data: disputesData } = useMyCasesQuery( + address, + disputeSkip, + decodedFilter, + order === "asc" ? OrderDirection.Asc : OrderDirection.Desc ); - - console.log("pages", totalPages); - - useEffect(() => { - setCurrentPage(1); - }, [statusFilter, courtFilter]); + const { data: userData } = useUserQuery(address, decodedFilter); + const totalCases = userData?.user?.disputes.length; return ( @@ -101,10 +57,12 @@ const Dashboard: React.FC = () => { navigate(`${location}/${newPage}/${order}/${filter}`)} + {...{ casesPerPage }} /> ) : ( diff --git a/web/src/utils/uri.ts b/web/src/utils/uri.ts new file mode 100644 index 000000000..659928e2d --- /dev/null +++ b/web/src/utils/uri.ts @@ -0,0 +1,23 @@ +import { useLocation } from "react-router-dom"; +import { Dispute_Filter } from "src/graphql/graphql"; + +export const encodeURIFilter = (filter: Dispute_Filter): string => { + if (Object.keys(filter).length === 0) { + return "all"; + } else { + return encodeURI(JSON.stringify(filter)); + } +}; + +export const decodeURIFilter = (filter: string): Dispute_Filter => { + if (filter === "all") { + return {}; + } else { + return JSON.parse(decodeURI(filter)); + } +}; + +export const useRootPath = () => { + const location = useLocation(); + return location.pathname.split("/").slice(0, -3).join("/"); +}; From 61c67b687bc63ca6ae4bdcb409cd64edc187558a Mon Sep 17 00:00:00 2001 From: alcercu <333aleix333@gmail.com> Date: Wed, 4 Oct 2023 17:53:31 +0200 Subject: [PATCH 28/36] chore(web): remove unused context --- web/src/components/CasesDisplay/CasesGrid.tsx | 11 ++- web/src/context/FilterProvider.tsx | 77 ------------------- 2 files changed, 7 insertions(+), 81 deletions(-) delete mode 100644 web/src/context/FilterProvider.tsx diff --git a/web/src/components/CasesDisplay/CasesGrid.tsx b/web/src/components/CasesDisplay/CasesGrid.tsx index 7a3ec3a6b..9e0e1956b 100644 --- a/web/src/components/CasesDisplay/CasesGrid.tsx +++ b/web/src/components/CasesDisplay/CasesGrid.tsx @@ -1,10 +1,11 @@ import React from "react"; import styled from "styled-components"; +import { useParams } from "react-router-dom"; import Skeleton from "react-loading-skeleton"; import { StandardPagination } from "@kleros/ui-components-library"; import { isUndefined } from "utils/index"; +import { decodeURIFilter } from "utils/uri"; import { DisputeDetailsFragment } from "queries/useCasesQuery"; -import { useFiltersContext } from "context/FilterProvider"; import DisputeCard from "components/DisputeCard"; const Container = styled.div` @@ -43,7 +44,9 @@ const CasesGrid: React.FC = ({ currentPage, setCurrentPage, }) => { - const { debouncedSearch } = useFiltersContext(); + const { filter } = useParams(); + const decodedFilter = decodeURIFilter(filter ?? "all"); + const { id: searchValue } = decodedFilter; return ( <> @@ -57,13 +60,13 @@ const CasesGrid: React.FC = ({ )} - {debouncedSearch === "" && ( + {isUndefined(searchValue) ? ( setCurrentPage(page)} /> - )} + ) : null} ); }; diff --git a/web/src/context/FilterProvider.tsx b/web/src/context/FilterProvider.tsx deleted file mode 100644 index 8c0225eb2..000000000 --- a/web/src/context/FilterProvider.tsx +++ /dev/null @@ -1,77 +0,0 @@ -import React, { useState, createContext, useContext, useMemo } from "react"; -import { useDebounce } from "react-use"; -import { Dispute_Filter, Period } from "src/graphql/graphql"; - -interface IFilters { - search: string; - setSearch: (arg0: string) => void; - debouncedSearch: string; - statusFilter: number; - timeFilter: number | string; - setTimeFilter: (arg0: number | string) => void; - combinedQueryFilters: Dispute_Filter; - setStatusFilter: (arg0: number) => void; -} - -const Context = createContext({ - search: "", - setSearch: () => { - // - }, - debouncedSearch: "", - statusFilter: 0, - setStatusFilter: () => { - // - }, - timeFilter: 0, - setTimeFilter: () => { - // - }, - - combinedQueryFilters: {}, -}); - -export const getStatusPeriod = (statusFilter: number) => { - switch (statusFilter) { - case 1: - return { ruled: false }; - case 2: - return { ruled: true }; - case 3: - return { period: Period.Appeal, ruled: false }; - default: - return {}; - } -}; - -export const FilterProvider: React.FC<{ children?: React.ReactNode }> = ({ children }) => { - const [statusFilter, setStatusFilter] = useState(0); - const [timeFilter, setTimeFilter] = useState(0); - const [search, setSearch] = useState(""); - const [debouncedSearch, setDebouncedSearch] = useState(""); - useDebounce(() => setDebouncedSearch(search), 500, [search]); - const periodStatus = getStatusPeriod(statusFilter); - const queryFilter = debouncedSearch ? { id: debouncedSearch } : undefined; - - const combinedQueryFilters = { - ...queryFilter, - ...periodStatus, - }; - - const value = useMemo( - () => ({ - search, - setSearch, - debouncedSearch, - timeFilter, - setTimeFilter, - statusFilter, - setStatusFilter, - combinedQueryFilters, - }), - [search, debouncedSearch, timeFilter, statusFilter] - ); - return {children}; -}; - -export const useFiltersContext = () => useContext(Context); From 6211b52e6c3cfe4748d9f919b7bc2bccb923bfee Mon Sep 17 00:00:00 2001 From: alcercu <333aleix333@gmail.com> Date: Wed, 4 Oct 2023 18:40:55 +0200 Subject: [PATCH 29/36] fix(web): finish merging dev --- web/src/components/CasesDisplay/index.tsx | 2 +- web/src/components/DisputeCard/DisputeInfo.tsx | 4 ++-- web/src/pages/Cases/CasesFetcher.tsx | 9 +++++++-- 3 files changed, 10 insertions(+), 5 deletions(-) diff --git a/web/src/components/CasesDisplay/index.tsx b/web/src/components/CasesDisplay/index.tsx index 9d5aa9b22..5a50795ca 100644 --- a/web/src/components/CasesDisplay/index.tsx +++ b/web/src/components/CasesDisplay/index.tsx @@ -10,6 +10,7 @@ const StyledHR = styled.hr` `; interface ICasesDisplay extends ICasesGrid { + numberDisputes?: number; numberClosedDisputes?: number; title?: string; className?: string; @@ -39,7 +40,6 @@ const CasesDisplay: React.FC = ({ ` display: flex; @@ -56,7 +56,7 @@ const formatDate = (date: number) => { }; const DisputeInfo: React.FC = ({ courtId, court, category, rewards, period, date, round }) => { - const { isList } = useFiltersContext(); + const [isList, _] = useLocalStorage("isList", false); return ( diff --git a/web/src/pages/Cases/CasesFetcher.tsx b/web/src/pages/Cases/CasesFetcher.tsx index 8e69ef672..a9ec8c607 100644 --- a/web/src/pages/Cases/CasesFetcher.tsx +++ b/web/src/pages/Cases/CasesFetcher.tsx @@ -1,11 +1,13 @@ import React from "react"; +import { useWindowSize } from "react-use"; import { useParams, useNavigate } from "react-router-dom"; import { DisputeDetailsFragment, Dispute_Filter, OrderDirection } from "src/graphql/graphql"; -import CasesDisplay from "components/CasesDisplay"; +import { BREAKPOINT_LANDSCAPE } from "styles/landscapeStyle"; import { useCasesQuery } from "queries/useCasesQuery"; import { useCounterQuery, CounterQuery } from "queries/useCounter"; import { useCourtDetails, CourtDetailsQuery } from "queries/useCourtDetails"; import { decodeURIFilter, useRootPath } from "utils/uri"; +import CasesDisplay from "components/CasesDisplay"; import { isUndefined } from "utils/index"; const calculateStats = ( @@ -40,7 +42,9 @@ const CasesFetcher: React.FC = () => { const { page, order, filter } = useParams(); const location = useRootPath(); const navigate = useNavigate(); - const casesPerPage = 3; + const { width } = useWindowSize(); + const screenIsBig = width > BREAKPOINT_LANDSCAPE; + const casesPerPage = screenIsBig ? 9 : 3; const pageNumber = parseInt(page ?? "1"); const disputeSkip = casesPerPage * (pageNumber - 1); const { data: counterData } = useCounterQuery(); @@ -49,6 +53,7 @@ const CasesFetcher: React.FC = () => { const { data: courtData } = useCourtDetails(decodedFilter?.court?.toString()); const { data } = useCasesQuery( disputeSkip, + casesPerPage, decodedFilter, order === "asc" ? OrderDirection.Asc : OrderDirection.Desc ); From 05746154d8edbae52e25a463f971f327f7140219 Mon Sep 17 00:00:00 2001 From: alcercu <333aleix333@gmail.com> Date: Thu, 5 Oct 2023 16:34:55 +0200 Subject: [PATCH 30/36] fix(web): fix list view problems --- web/src/app.tsx | 23 +++++----- web/src/components/CasesDisplay/CasesGrid.tsx | 25 ++++++----- web/src/components/CasesDisplay/Filters.tsx | 5 ++- .../components/DisputeCard/DisputeInfo.tsx | 42 ++++++++++++++----- web/src/components/DisputeCard/index.tsx | 14 ++++--- web/src/components/Field.tsx | 10 ++--- web/src/context/FilterProvider.tsx | 25 ----------- web/src/context/IsListProvider.tsx | 33 +++++++++++++++ web/src/pages/Cases/CaseDetails/Overview.tsx | 10 +---- web/src/pages/Home/LatestCases.tsx | 16 +++---- 10 files changed, 116 insertions(+), 87 deletions(-) delete mode 100644 web/src/context/FilterProvider.tsx create mode 100644 web/src/context/IsListProvider.tsx diff --git a/web/src/app.tsx b/web/src/app.tsx index f0f11af3e..a1f35108d 100644 --- a/web/src/app.tsx +++ b/web/src/app.tsx @@ -4,6 +4,7 @@ import { SentryRoutes } from "./utils/sentry"; import "react-loading-skeleton/dist/skeleton.css"; import "react-toastify/dist/ReactToastify.css"; import Web3Provider from "context/Web3Provider"; +import IsListProvider from "context/IsListProvider"; import QueryClientProvider from "context/QueryClientProvider"; import StyledComponentsProvider from "context/StyledComponentsProvider"; import RefetchOnBlock from "context/RefetchOnBlock"; @@ -20,16 +21,18 @@ const App: React.FC = () => { - - }> - } /> - } /> - } /> - } /> - } /> - Justice not found here ¯\_( ͡° ͜ʖ ͡°)_/¯} /> - - + + + }> + } /> + } /> + } /> + } /> + } /> + Justice not found here ¯\_( ͡° ͜ʖ ͡°)_/¯} /> + + + diff --git a/web/src/components/CasesDisplay/CasesGrid.tsx b/web/src/components/CasesDisplay/CasesGrid.tsx index 2e694ebd5..faf39e55d 100644 --- a/web/src/components/CasesDisplay/CasesGrid.tsx +++ b/web/src/components/CasesDisplay/CasesGrid.tsx @@ -1,10 +1,11 @@ -import React from "react"; +import React, { useMemo } from "react"; import styled, { css } from "styled-components"; -import { useLocalStorage } from "react-use"; +import { useWindowSize } from "react-use"; import { useParams } from "react-router-dom"; import Skeleton from "react-loading-skeleton"; import { StandardPagination } from "@kleros/ui-components-library"; -import { landscapeStyle } from "styles/landscapeStyle"; +import { BREAKPOINT_LANDSCAPE, landscapeStyle } from "styles/landscapeStyle"; +import { useIsList } from "context/IsListProvider"; import { isUndefined } from "utils/index"; import { decodeURIFilter } from "utils/uri"; import { DisputeDetailsFragment } from "queries/useCasesQuery"; @@ -60,27 +61,29 @@ const CasesGrid: React.FC = ({ disputes, casesPerPage, totalPages, c const { filter } = useParams(); const decodedFilter = decodeURIFilter(filter ?? "all"); const { id: searchValue } = decodedFilter; - const [isList, _] = useLocalStorage("isList", false); + const { isList } = useIsList(); + const { width } = useWindowSize(); + const screenIsBig = useMemo(() => width > BREAKPOINT_LANDSCAPE, [width]); return ( <> - {!isList ? ( - + {isList && screenIsBig ? ( + + {isUndefined(disputes) ? [...Array(casesPerPage)].map((_, i) => ) : disputes.map((dispute, i) => { return ; })} - + ) : ( - - + {isUndefined(disputes) ? [...Array(casesPerPage)].map((_, i) => ) : disputes.map((dispute, i) => { - return ; + return ; })} - + )} {isUndefined(searchValue) ? ( diff --git a/web/src/components/CasesDisplay/Filters.tsx b/web/src/components/CasesDisplay/Filters.tsx index 94b98426d..b1772f096 100644 --- a/web/src/components/CasesDisplay/Filters.tsx +++ b/web/src/components/CasesDisplay/Filters.tsx @@ -1,8 +1,9 @@ import React from "react"; import styled, { useTheme } from "styled-components"; import { useNavigate, useParams } from "react-router-dom"; -import { useWindowSize, useLocalStorage } from "react-use"; +import { useWindowSize } from "react-use"; import { DropdownSelect } from "@kleros/ui-components-library"; +import { useIsList } from "context/IsListProvider"; import ListIcon from "svgs/icons/list.svg"; import GridIcon from "svgs/icons/grid.svg"; import { BREAKPOINT_LANDSCAPE } from "styles/landscapeStyle"; @@ -59,7 +60,7 @@ const Filters: React.FC = () => { }; const { width } = useWindowSize(); - const [isList, setIsList] = useLocalStorage("isList", false); + const { isList, setIsList } = useIsList(); const screenIsBig = width > BREAKPOINT_LANDSCAPE; return ( diff --git a/web/src/components/DisputeCard/DisputeInfo.tsx b/web/src/components/DisputeCard/DisputeInfo.tsx index 9ec26ea85..9a537f4ef 100644 --- a/web/src/components/DisputeCard/DisputeInfo.tsx +++ b/web/src/components/DisputeCard/DisputeInfo.tsx @@ -1,13 +1,13 @@ import React from "react"; import styled, { css } from "styled-components"; import { Periods } from "consts/periods"; +import { useIsList } from "context/IsListProvider"; import BookmarkIcon from "svgs/icons/bookmark.svg"; import CalendarIcon from "svgs/icons/calendar.svg"; import LawBalanceIcon from "svgs/icons/law-balance.svg"; import PileCoinsIcon from "svgs/icons/pile-coins.svg"; import RoundIcon from "svgs/icons/round.svg"; import Field from "../Field"; -import { useLocalStorage } from "react-use"; const Container = styled.div<{ isList: boolean }>` display: flex; @@ -46,6 +46,7 @@ export interface IDisputeInfo { period?: Periods; date?: number; round?: number; + overrideIsList?: boolean; } const formatDate = (date: number) => { @@ -55,21 +56,42 @@ const formatDate = (date: number) => { return formattedDate; }; -const DisputeInfo: React.FC = ({ courtId, court, category, rewards, period, date, round }) => { - const [isList, _] = useLocalStorage("isList", false); +const DisputeInfo: React.FC = ({ + courtId, + court, + category, + rewards, + period, + date, + round, + overrideIsList, +}) => { + const { isList } = useIsList(); + const displayAsList = isList && !overrideIsList; return ( - - {court && courtId && } - {category && } - {!category && isList && } - {round && } - {rewards && } + + {court && courtId && ( + + )} + {category && } + {!category && displayAsList && ( + + )} + {round && } + {rewards && } {typeof period !== "undefined" && date && ( )} diff --git a/web/src/components/DisputeCard/index.tsx b/web/src/components/DisputeCard/index.tsx index 6a4d4e8c8..fcc48a9c7 100644 --- a/web/src/components/DisputeCard/index.tsx +++ b/web/src/components/DisputeCard/index.tsx @@ -1,11 +1,11 @@ import React from "react"; import styled, { css } from "styled-components"; -import { useLocalStorage } from "react-use"; import { useNavigate } from "react-router-dom"; import { formatEther } from "viem"; import { StyledSkeleton } from "components/StyledSkeleton"; import { Card } from "@kleros/ui-components-library"; import { Periods } from "consts/periods"; +import { useIsList } from "context/IsListProvider"; import { DisputeDetailsFragment } from "queries/useCasesQuery"; import { landscapeStyle } from "styles/landscapeStyle"; import { useCourtPolicy } from "queries/useCourtPolicy"; @@ -76,8 +76,12 @@ const TruncatedTitle = ({ text, maxLength }) => { return

{truncatedText}

; }; -const DisputeCard: React.FC = ({ id, arbitrated, period, lastPeriodChange, court }) => { - const [isList, _] = useLocalStorage("isList", false); +interface IDisputeCard extends DisputeDetailsFragment { + overrideIsList?: boolean; +} + +const DisputeCard: React.FC = ({ id, arbitrated, period, lastPeriodChange, court, overrideIsList }) => { + const { isList } = useIsList(); const currentPeriodIndex = Periods[period]; const rewards = `≥ ${formatEther(court.feeForJuror)} ETH`; const date = @@ -98,7 +102,7 @@ const DisputeCard: React.FC = ({ id, arbitrated, period, const navigate = useNavigate(); return ( <> - {!isList ? ( + {!isList || overrideIsList ? ( navigate(`/cases/${id.toString()}`)}> @@ -108,7 +112,7 @@ const DisputeCard: React.FC = ({ id, arbitrated, period, court={courtName} period={currentPeriodIndex} round={localRounds?.length} - {...{ category, rewards, date }} + {...{ category, rewards, date, overrideIsList }} /> diff --git a/web/src/components/Field.tsx b/web/src/components/Field.tsx index 15a08ce6e..9291f97ca 100644 --- a/web/src/components/Field.tsx +++ b/web/src/components/Field.tsx @@ -1,7 +1,7 @@ import React from "react"; import { Link } from "react-router-dom"; import styled from "styled-components"; -import { useFiltersContext } from "context/FilterProvider"; +import { useIsList } from "context/IsListProvider"; const FieldContainer = styled.div` width: ${({ isList }) => (isList ? "auto" : "100%")}; @@ -38,13 +38,13 @@ interface IField { value: string; link?: string; width?: string; + displayAsList?: boolean; } -const Field: React.FC = ({ icon: Icon, name, value, link, width }) => { - const { isList } = useFiltersContext(); +const Field: React.FC = ({ icon: Icon, name, value, link, width, displayAsList }) => { return ( - - {!isList && ( + + {!displayAsList && ( <> diff --git a/web/src/context/FilterProvider.tsx b/web/src/context/FilterProvider.tsx deleted file mode 100644 index 267059c5e..000000000 --- a/web/src/context/FilterProvider.tsx +++ /dev/null @@ -1,25 +0,0 @@ -import React, { useState, createContext, useContext } from "react"; - -interface IFilters { - isList: boolean; - setIsList: (arg0: boolean) => void; -} - -const Context = createContext({ - isList: false, - setIsList: () => { - // - }, -}); - -export const FilterProvider: React.FC<{ children?: React.ReactNode }> = ({ children }) => { - const [isList, setIsList] = useState(false); - - const value = { - isList, - setIsList, - }; - return {children}; -}; - -export const useFiltersContext = () => useContext(Context); diff --git a/web/src/context/IsListProvider.tsx b/web/src/context/IsListProvider.tsx new file mode 100644 index 000000000..45aaafea0 --- /dev/null +++ b/web/src/context/IsListProvider.tsx @@ -0,0 +1,33 @@ +import React, { createContext, useContext } from "react"; +import { useLocalStorage, useToggle } from "react-use"; + +interface IIsListProvider { + isList: boolean; + setIsList: (arg0: boolean) => void; +} + +const Context = createContext({ + isList: false, + setIsList: () => { + // + }, +}); + +const IsListProvider: React.FC<{ children?: React.ReactNode }> = ({ children }) => { + const [isListStorage, setIsListStorage] = useLocalStorage("isList", false); + const [isList, setIsListState] = useToggle(isListStorage ?? false); + const setIsList = (value: boolean) => { + setIsListState(value); + setIsListStorage(value); + }; + + const value = { + isList, + setIsList, + }; + return {children}; +}; + +export const useIsList = () => useContext(Context); + +export default IsListProvider; diff --git a/web/src/pages/Cases/CaseDetails/Overview.tsx b/web/src/pages/Cases/CaseDetails/Overview.tsx index c6b414ee8..470c328cf 100644 --- a/web/src/pages/Cases/CaseDetails/Overview.tsx +++ b/web/src/pages/Cases/CaseDetails/Overview.tsx @@ -1,4 +1,4 @@ -import React, { useEffect } from "react"; +import React from "react"; import styled, { css } from "styled-components"; import { landscapeStyle } from "styles/landscapeStyle"; import { useParams } from "react-router-dom"; @@ -7,7 +7,6 @@ import { formatEther } from "viem"; import { useDisputeDetailsQuery } from "queries/useDisputeDetailsQuery"; import { useDisputeTemplate } from "queries/useDisputeTemplate"; import { useCourtPolicy } from "queries/useCourtPolicy"; -import { useFiltersContext } from "context/FilterProvider"; import { isUndefined } from "utils/index"; import { Periods } from "consts/periods"; import { IPFS_GATEWAY } from "consts/index"; @@ -117,7 +116,6 @@ const Overview: React.FC = ({ arbitrable, courtID, currentPeriodIndex const { data: disputeTemplate } = useDisputeTemplate(id, arbitrable); const { data: disputeDetails } = useDisputeDetailsQuery(id); const { data: courtPolicy } = useCourtPolicy(courtID); - const { isList, setIsList } = useFiltersContext(); const { data: votingHistory } = useVotingHistory(id); const localRounds = votingHistory?.dispute?.disputeKitDispute?.localRounds; const courtName = courtPolicy?.name; @@ -125,12 +123,6 @@ const Overview: React.FC = ({ arbitrable, courtID, currentPeriodIndex const rewards = court ? `≥ ${formatEther(court.feeForJuror)} ETH` : undefined; const category = disputeTemplate ? disputeTemplate.category : undefined; - useEffect(() => { - if (isList) { - setIsList(false); - } - }, []); - return ( <> diff --git a/web/src/pages/Home/LatestCases.tsx b/web/src/pages/Home/LatestCases.tsx index 7ebd3122c..de43a5142 100644 --- a/web/src/pages/Home/LatestCases.tsx +++ b/web/src/pages/Home/LatestCases.tsx @@ -1,9 +1,9 @@ -import React, { useEffect } from "react"; +import React, { useMemo } from "react"; import styled from "styled-components"; -import { useCasesQuery } from "queries/useCasesQuery"; -import { useFiltersContext } from "context/FilterProvider"; +import { DisputeDetailsFragment, useCasesQuery } from "queries/useCasesQuery"; import DisputeCard from "components/DisputeCard"; import { StyledSkeleton } from "components/StyledSkeleton"; +import { isUndefined } from "utils/index"; const Container = styled.div` margin-top: calc(64px + (80 - 64) * (min(max(100vw, 375px), 1250px) - 375px) / 875); @@ -21,18 +21,14 @@ const Container = styled.div` const LatestCases: React.FC = () => { const { data } = useCasesQuery(0); - const { setIsList } = useFiltersContext(); - - useEffect(() => { - setIsList(false); - }, []); + const disputes: DisputeDetailsFragment[] = useMemo(() => data?.disputes as DisputeDetailsFragment[], [data]); return (

Latest Cases

- {data - ? data.disputes.map((dispute) => ) + {!isUndefined(disputes) + ? disputes.map((dispute) => ) : Array.from({ length: 3 }).map((_, index) => )}
From 9a095691bcf9b0bf65a8dfcc15a4c7921047e9c7 Mon Sep 17 00:00:00 2001 From: alcercu <333aleix333@gmail.com> Date: Thu, 5 Oct 2023 17:46:05 +0200 Subject: [PATCH 31/36] fix(web): code smells --- web/src/components/CasesDisplay/CasesGrid.tsx | 8 ++++---- web/src/components/Field.tsx | 1 - web/src/hooks/queries/useCourtTree.ts | 10 +++++----- 3 files changed, 9 insertions(+), 10 deletions(-) diff --git a/web/src/components/CasesDisplay/CasesGrid.tsx b/web/src/components/CasesDisplay/CasesGrid.tsx index faf39e55d..31178ffe7 100644 --- a/web/src/components/CasesDisplay/CasesGrid.tsx +++ b/web/src/components/CasesDisplay/CasesGrid.tsx @@ -72,16 +72,16 @@ const CasesGrid: React.FC = ({ disputes, casesPerPage, totalPages, c {isUndefined(disputes) ? [...Array(casesPerPage)].map((_, i) => ) - : disputes.map((dispute, i) => { - return ; + : disputes.map((dispute) => { + return ; })} ) : ( {isUndefined(disputes) ? [...Array(casesPerPage)].map((_, i) => ) - : disputes.map((dispute, i) => { - return ; + : disputes.map((dispute) => { + return ; })} )} diff --git a/web/src/components/Field.tsx b/web/src/components/Field.tsx index 9291f97ca..c29842c6b 100644 --- a/web/src/components/Field.tsx +++ b/web/src/components/Field.tsx @@ -1,7 +1,6 @@ import React from "react"; import { Link } from "react-router-dom"; import styled from "styled-components"; -import { useIsList } from "context/IsListProvider"; const FieldContainer = styled.div` width: ${({ isList }) => (isList ? "auto" : "100%")}; diff --git a/web/src/hooks/queries/useCourtTree.ts b/web/src/hooks/queries/useCourtTree.ts index 532aa5acf..52d82a0a6 100644 --- a/web/src/hooks/queries/useCourtTree.ts +++ b/web/src/hooks/queries/useCourtTree.ts @@ -34,7 +34,7 @@ const courtTreeQuery = graphql(` `); export const useCourtTree = () => { - return useQuery({ + return useQuery({ queryKey: ["courtTreeQuery"], queryFn: async () => await graphqlQueryFnHelper(courtTreeQuery, {}), }); @@ -46,8 +46,8 @@ interface IItem { children?: IItem[]; } -export const rootCourtToItems = (court: CourtTreeQuery["court"], value?: "id" | "path"): IItem => ({ - label: court!.name ? court!.name : "Unnamed Court", - value: value === "id" ? court!.id : `/courts/${court!.id}`, - children: court!.children.length > 0 ? court!.children.map((child) => rootCourtToItems(child)) : undefined, +export const rootCourtToItems = (court: NonNullable, value?: "id" | "path"): IItem => ({ + label: court.name ? court.name : "Unnamed Court", + value: value === "id" ? court.id : `/courts/${court.id}`, + children: court.children.length > 0 ? court.children.map((child) => rootCourtToItems(child)) : undefined, }); From 2048cfff9712c51104b73c769792c749144761d7 Mon Sep 17 00:00:00 2001 From: alcercu <333aleix333@gmail.com> Date: Fri, 6 Oct 2023 19:13:09 +0200 Subject: [PATCH 32/36] fix(web): wrong count and bad filter encoding --- web/src/components/CasesDisplay/Search.tsx | 20 ++++++++++++++------ web/src/hooks/queries/useCourtTree.ts | 7 +++++-- web/src/pages/Cases/CasesFetcher.tsx | 12 +++++------- 3 files changed, 24 insertions(+), 15 deletions(-) diff --git a/web/src/components/CasesDisplay/Search.tsx b/web/src/components/CasesDisplay/Search.tsx index cecda95e8..dc13b52be 100644 --- a/web/src/components/CasesDisplay/Search.tsx +++ b/web/src/components/CasesDisplay/Search.tsx @@ -4,7 +4,7 @@ import { useDebounce } from "react-use"; import styled from "styled-components"; import Skeleton from "react-loading-skeleton"; import { Searchbar, DropdownCascader } from "@kleros/ui-components-library"; -import { rootCourtToItems, useCourtTree } from "hooks/queries/useCourtTree"; +import { rootCourtToItems, useCourtTree } from "queries/useCourtTree"; import { isUndefined } from "utils/index"; import { decodeURIFilter, encodeURIFilter, useRootPath } from "utils/uri"; @@ -44,10 +44,14 @@ const Search: React.FC = () => { [search] ); const { data: courtTreeData } = useCourtTree(); - const items = useMemo( - () => !isUndefined(courtTreeData) && [rootCourtToItems(courtTreeData.court, "id")], - [courtTreeData] - ); + const items = useMemo(() => { + if (!isUndefined(courtTreeData)) { + const items = [rootCourtToItems(courtTreeData.court!, "id")]; + items.push({ label: "All Courts", value: "all" }); + return items; + } + return undefined; + }, [courtTreeData]); return (
@@ -63,7 +67,11 @@ const Search: React.FC = () => { navigate(`${location}/${page}/${order}/${{ ...filterObject, court: value }}`)} + onSelect={(value) => { + const { court: _, ...filterWithoutCourt } = decodedFilter; + const newFilter = value === "all" ? filterWithoutCourt : { ...decodedFilter, court: value.toString() }; + navigate(`${location}/${page}/${order}/${encodeURIFilter(newFilter)}`); + }} /> ) : ( diff --git a/web/src/hooks/queries/useCourtTree.ts b/web/src/hooks/queries/useCourtTree.ts index 52d82a0a6..9b9b4d31c 100644 --- a/web/src/hooks/queries/useCourtTree.ts +++ b/web/src/hooks/queries/useCourtTree.ts @@ -46,8 +46,11 @@ interface IItem { children?: IItem[]; } -export const rootCourtToItems = (court: NonNullable, value?: "id" | "path"): IItem => ({ +export const rootCourtToItems = ( + court: NonNullable, + value: "id" | "path" = "path" +): IItem => ({ label: court.name ? court.name : "Unnamed Court", value: value === "id" ? court.id : `/courts/${court.id}`, - children: court.children.length > 0 ? court.children.map((child) => rootCourtToItems(child)) : undefined, + children: court.children.length > 0 ? court.children.map((child) => rootCourtToItems(child, value)) : undefined, }); diff --git a/web/src/pages/Cases/CasesFetcher.tsx b/web/src/pages/Cases/CasesFetcher.tsx index a9ec8c607..80f8cc762 100644 --- a/web/src/pages/Cases/CasesFetcher.tsx +++ b/web/src/pages/Cases/CasesFetcher.tsx @@ -1,4 +1,4 @@ -import React from "react"; +import React, { useMemo } from "react"; import { useWindowSize } from "react-use"; import { useParams, useNavigate } from "react-router-dom"; import { DisputeDetailsFragment, Dispute_Filter, OrderDirection } from "src/graphql/graphql"; @@ -24,7 +24,7 @@ const calculateStats = ( totalCases = isCourtFilter ? courtData?.numberDisputes : counters?.cases; ruledCases = isCourtFilter ? courtData?.numberClosedDisputes : counters?.casesRuled; } else if (filter?.ruled) { - totalCases = isCourtFilter ? courtData?.numberClosedDisputes : counters?.casesAppealing; + totalCases = isCourtFilter ? courtData?.numberClosedDisputes : counters?.casesRuled; ruledCases = totalCases; } else { totalCases = isCourtFilter @@ -57,11 +57,9 @@ const CasesFetcher: React.FC = () => { decodedFilter, order === "asc" ? OrderDirection.Asc : OrderDirection.Desc ); - const { totalCases, ruledCases } = calculateStats( - isCourtFilter, - courtData?.court, - counterData?.counter, - decodedFilter + const { totalCases, ruledCases } = useMemo( + () => calculateStats(isCourtFilter, courtData?.court, counterData?.counter, decodedFilter), + [isCourtFilter, courtData?.court, counterData?.counter, decodedFilter] ); return ( From f29a40d9def7fad2cc354db4fae7214f161872c6 Mon Sep 17 00:00:00 2001 From: alcercu <333aleix333@gmail.com> Date: Fri, 6 Oct 2023 19:13:49 +0200 Subject: [PATCH 33/36] fix(subgraph): voting and appealing cases count --- subgraph/src/KlerosCore.ts | 8 ++++---- subgraph/src/entities/Round.ts | 21 ++++++++++----------- 2 files changed, 14 insertions(+), 15 deletions(-) diff --git a/subgraph/src/KlerosCore.ts b/subgraph/src/KlerosCore.ts index 738b39043..3c0efc480 100644 --- a/subgraph/src/KlerosCore.ts +++ b/subgraph/src/KlerosCore.ts @@ -90,13 +90,13 @@ export function handleNewPeriod(event: NewPeriod): void { const court = Court.load(dispute.court); if (!court) return; - if (dispute.period === "vote") { + if (dispute.period.includes("vote")) { court.numberVotingDisputes = court.numberVotingDisputes.minus(ONE); updateCasesVoting(BigInt.fromI32(-1), event.block.timestamp); - } else if (dispute.period === "appeal") { + } else if (dispute.period.includes("appeal")) { let juror: User; for (let i = 0; i < dispute.jurors.entries.length; i++) { - juror = ensureUser(dispute.jurors.entries[0].value.toString()); + juror = ensureUser(dispute.jurors.entries[i].value.toString()); juror.totalAppealingDisputes = juror.totalAppealingDisputes.minus(ONE); juror.save(); } @@ -111,7 +111,7 @@ export function handleNewPeriod(event: NewPeriod): void { } else if (newPeriod === "appeal") { let juror: User; for (let i = 0; i < dispute.jurors.entries.length; i++) { - juror = ensureUser(dispute.jurors.entries[0].value.toString()); + juror = ensureUser(dispute.jurors.entries[i].value.toString()); juror.totalAppealingDisputes = juror.totalAppealingDisputes.plus(ONE); juror.save(); } diff --git a/subgraph/src/entities/Round.ts b/subgraph/src/entities/Round.ts index 39fb2c038..731f43773 100644 --- a/subgraph/src/entities/Round.ts +++ b/subgraph/src/entities/Round.ts @@ -1,23 +1,22 @@ import { BigInt } from "@graphprotocol/graph-ts"; -import { KlerosCore__getRoundInfoResultValue0Struct } from "../../generated/KlerosCore/KlerosCore"; +import { KlerosCore__getRoundInfoResult } from "../../generated/KlerosCore/KlerosCore"; import { Round } from "../../generated/schema"; export function createRoundFromRoundInfo( disputeID: BigInt, roundIndex: BigInt, - roundInfo: KlerosCore__getRoundInfoResultValue0Struct + roundInfo: KlerosCore__getRoundInfoResult ): void { const roundID = `${disputeID.toString()}-${roundIndex.toString()}`; const round = new Round(roundID); - const feeToken = roundInfo.feeToken; - round.disputeKit = roundInfo.disputeKitID.toString(); - round.tokensAtStakePerJuror = roundInfo.pnkAtStakePerJuror; - round.totalFeesForJurors = roundInfo.totalFeesForJurors; - round.nbVotes = roundInfo.nbVotes; - round.repartitions = roundInfo.repartitions; - round.penalties = roundInfo.pnkPenalties; + const feeToken = roundInfo.getFeeToken().toHexString(); + round.feeToken = feeToken === "0x0000000000000000000000000000000000000000" ? null : feeToken; + round.disputeKit = roundInfo.getDisputeKitID().toString(); + round.tokensAtStakePerJuror = roundInfo.getPnkAtStakePerJuror(); + round.totalFeesForJurors = roundInfo.getTotalFeesForJurors(); + round.nbVotes = roundInfo.getNbVotes(); + round.repartitions = roundInfo.getRepartitions(); + round.penalties = roundInfo.getPnkPenalties(); round.dispute = disputeID.toString(); - round.feeToken = - feeToken.toHexString() === "0x0000000000000000000000000000000000000000" ? null : feeToken.toHexString(); round.save(); } From a0411a66bdc9d01a2b15c1ca7a71b3ef5b766722 Mon Sep 17 00:00:00 2001 From: jaybuidl Date: Sat, 7 Oct 2023 00:55:22 +0100 Subject: [PATCH 34/36] chore: filter ordering --- web/src/components/CasesDisplay/Filters.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/src/components/CasesDisplay/Filters.tsx b/web/src/components/CasesDisplay/Filters.tsx index b1772f096..01b5cc639 100644 --- a/web/src/components/CasesDisplay/Filters.tsx +++ b/web/src/components/CasesDisplay/Filters.tsx @@ -71,8 +71,8 @@ const Filters: React.FC = () => { items={[ { value: JSON.stringify({}), text: "All Cases", dot: theme.primaryText }, { value: JSON.stringify({ ruled: false }), text: "In Progress", dot: theme.primaryBlue }, - { value: JSON.stringify({ ruled: true }), text: "Closed", dot: theme.primaryPurple }, { value: JSON.stringify({ period: "appeal" }), text: "Appeal", dot: theme.tint }, + { value: JSON.stringify({ ruled: true }), text: "Closed", dot: theme.primaryPurple }, ]} defaultValue={JSON.stringify({ ruled, period })} callback={handleStatusChange} From a1c67ca936ecaac053f864ecb00b903d70d770b3 Mon Sep 17 00:00:00 2001 From: jaybuidl Date: Sat, 7 Oct 2023 00:56:59 +0100 Subject: [PATCH 35/36] chore: dispute period banner more specific --- web/src/components/DisputeCard/PeriodBanner.tsx | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/web/src/components/DisputeCard/PeriodBanner.tsx b/web/src/components/DisputeCard/PeriodBanner.tsx index 0ad1ac647..d85d681c7 100644 --- a/web/src/components/DisputeCard/PeriodBanner.tsx +++ b/web/src/components/DisputeCard/PeriodBanner.tsx @@ -57,6 +57,12 @@ const getPeriodColors = (period: Periods, theme: Theme): [string, string] => { const getPeriodLabel = (period: Periods): string => { switch (period) { + case Periods.evidence: + return "In Progress - Submitting Evidence"; + case Periods.commit: + return "In Progress - Committing Vote"; + case Periods.vote: + return "In Progress - Voting"; case Periods.appeal: return "Crowdfunding Appeal"; case Periods.execution: From f42cc1497c009fcb719db20a3de37401ede3b6f5 Mon Sep 17 00:00:00 2001 From: jaybuidl Date: Sat, 7 Oct 2023 01:19:46 +0100 Subject: [PATCH 36/36] fix: linter about shadowed variables --- web/src/components/CasesDisplay/Search.tsx | 6 +++--- web/src/context/IsListProvider.tsx | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/web/src/components/CasesDisplay/Search.tsx b/web/src/components/CasesDisplay/Search.tsx index dc13b52be..6bb6e4b5f 100644 --- a/web/src/components/CasesDisplay/Search.tsx +++ b/web/src/components/CasesDisplay/Search.tsx @@ -46,9 +46,9 @@ const Search: React.FC = () => { const { data: courtTreeData } = useCourtTree(); const items = useMemo(() => { if (!isUndefined(courtTreeData)) { - const items = [rootCourtToItems(courtTreeData.court!, "id")]; - items.push({ label: "All Courts", value: "all" }); - return items; + const courts = [rootCourtToItems(courtTreeData.court!, "id")]; + courts.push({ label: "All Courts", value: "all" }); + return courts; } return undefined; }, [courtTreeData]); diff --git a/web/src/context/IsListProvider.tsx b/web/src/context/IsListProvider.tsx index 45aaafea0..1525f05a5 100644 --- a/web/src/context/IsListProvider.tsx +++ b/web/src/context/IsListProvider.tsx @@ -16,9 +16,9 @@ const Context = createContext({ const IsListProvider: React.FC<{ children?: React.ReactNode }> = ({ children }) => { const [isListStorage, setIsListStorage] = useLocalStorage("isList", false); const [isList, setIsListState] = useToggle(isListStorage ?? false); - const setIsList = (value: boolean) => { - setIsListState(value); - setIsListStorage(value); + const setIsList = (toggle: boolean) => { + setIsListState(toggle); + setIsListStorage(toggle); }; const value = {