diff --git a/web/package.json b/web/package.json index 663d950b7..ddb99aa15 100644 --- a/web/package.json +++ b/web/package.json @@ -58,7 +58,8 @@ "@graphql-codegen/typescript": "^2.7.3", "@graphql-codegen/typescript-operations": "^2.5.3", "@kleros/kleros-v2-contracts": "workspace:^", - "@kleros/ui-components-library": "^1.7.0", + "@kleros/ui-components-library": "^1.8.1", + "@types/react-modal": "^3.13.1", "@web3-react/core": "^6.1.9", "@web3-react/injected-connector": "^6.0.7", "@web3-react/types": "^6.0.7", @@ -74,7 +75,10 @@ "react-dom": "^18.2.0", "react-error-boundary": "^3.1.4", "react-is": "^18.2.0", - "react-router-dom": "6", + "react-jazzicon": "^1.0.4", + "react-loading-skeleton": "^3.1.0", + "react-modal": "^3.15.1", + "react-router-dom": "^6.4.0", "styled-components": "^5.3.5", "swr": "^1.3.0" } diff --git a/web/src/app.tsx b/web/src/app.tsx index e03e75d9e..f27ab1889 100644 --- a/web/src/app.tsx +++ b/web/src/app.tsx @@ -2,6 +2,7 @@ import React from "react"; import { SWRConfig } from "swr"; import { request } from "graphql-request"; import { Routes, Route } from "react-router-dom"; +import "react-loading-skeleton/dist/skeleton.css"; import Web3Provider from "context/Web3Provider"; import StyledComponentsProvider from "context/StyledComponentsProvider"; import WrongChainBoundary from "components/WrongChainBoundary"; @@ -13,7 +14,7 @@ import Dashboard from "./pages/Dashboard"; const fetcherBuilder = (url: string) => ({ query, variables }: { query: string; variables?: any }) => { - console.log("fetch"); + console.log("fetching subgraph"); return request(url, query, variables); }; @@ -32,7 +33,7 @@ const App: React.FC = () => { }> } /> - } /> + } /> Courts} /> } /> + + + + + + + + + diff --git a/web/src/assets/svgs/icons/bullhorn.svg b/web/src/assets/svgs/icons/bullhorn.svg new file mode 100644 index 000000000..e3ad46660 --- /dev/null +++ b/web/src/assets/svgs/icons/bullhorn.svg @@ -0,0 +1,3 @@ + + + diff --git a/web/src/assets/svgs/icons/doc.svg b/web/src/assets/svgs/icons/doc.svg new file mode 100644 index 000000000..bf2adf79d --- /dev/null +++ b/web/src/assets/svgs/icons/doc.svg @@ -0,0 +1,3 @@ + + + diff --git a/web/src/assets/svgs/icons/eye.svg b/web/src/assets/svgs/icons/eye.svg new file mode 100644 index 000000000..c0ca4bf57 --- /dev/null +++ b/web/src/assets/svgs/icons/eye.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/web/src/assets/svgs/icons/policy.svg b/web/src/assets/svgs/icons/policy.svg new file mode 100644 index 000000000..14de6afe1 --- /dev/null +++ b/web/src/assets/svgs/icons/policy.svg @@ -0,0 +1,3 @@ + + + diff --git a/web/src/components/CasesDisplay/CasesGrid.tsx b/web/src/components/CasesDisplay/CasesGrid.tsx index 09ee766cb..a552db8fb 100644 --- a/web/src/components/CasesDisplay/CasesGrid.tsx +++ b/web/src/components/CasesDisplay/CasesGrid.tsx @@ -1,6 +1,7 @@ import React from "react"; import styled from "styled-components"; import { StandardPagination } from "@kleros/ui-components-library"; +import { CasesPageQuery } from "queries/useCasesQuery"; import DisputeCard from "components/DisputeCard"; const Container = styled.div` @@ -18,42 +19,34 @@ const StyledPagination = styled(StandardPagination)` `; export interface ICasesGrid { + disputes: CasesPageQuery["disputes"]; + currentPage: number; + setCurrentPage: (newPage: number) => void; + numberDisputes: number; casesPerPage: number; } -const CasesGrid: React.FC = ({ casesPerPage }) => ( - <> - - = ({ + disputes, + currentPage, + setCurrentPage, + numberDisputes, + casesPerPage, +}) => { + return ( + <> + + {disputes.map((dispute, i) => { + return ; + })} + + setCurrentPage(page)} /> - - - - {}} /> - -); + + ); +}; export default CasesGrid; diff --git a/web/src/components/CasesDisplay/index.tsx b/web/src/components/CasesDisplay/index.tsx index d924a1bca..bb6ae4b21 100644 --- a/web/src/components/CasesDisplay/index.tsx +++ b/web/src/components/CasesDisplay/index.tsx @@ -15,6 +15,10 @@ interface ICasesDisplay extends ICasesGrid { } const CasesDisplay: React.FC = ({ + disputes, + currentPage, + setCurrentPage, + numberDisputes, casesPerPage, title = "Cases", className, @@ -24,7 +28,15 @@ const CasesDisplay: React.FC = ({ - + ); diff --git a/web/src/components/DisputeCard/DisputeInfo.tsx b/web/src/components/DisputeCard/DisputeInfo.tsx index f52557d67..327096d96 100644 --- a/web/src/components/DisputeCard/DisputeInfo.tsx +++ b/web/src/components/DisputeCard/DisputeInfo.tsx @@ -45,9 +45,11 @@ const Container = styled.div` const getPeriodPhrase = (period: Periods) => { switch (period) { - case Periods.appeal: + case Periods.Evidence: + return "Voting Starts"; + case Periods.Appeal: return "Appeal Deadline"; - case Periods.execution: + case Periods.Execution: return "Final Decision"; default: return "Voting Deadline"; @@ -55,11 +57,11 @@ const getPeriodPhrase = (period: Periods) => { }; export interface IDisputeInfo { - court: string; - category: string; - rewards: string; - period: Periods; - date: number; + court?: string; + category?: string; + rewards?: string; + period?: Periods; + date?: number; } const DisputeInfo: React.FC = ({ @@ -70,14 +72,18 @@ const DisputeInfo: React.FC = ({ date, }) => ( - - - - + {category && } + {court && } + {rewards && ( + + )} + {typeof period !== "undefined" && date && ( + + )} ); diff --git a/web/src/components/DisputeCard/PeriodBanner.tsx b/web/src/components/DisputeCard/PeriodBanner.tsx index 7e1ee779f..4d22fa41e 100644 --- a/web/src/components/DisputeCard/PeriodBanner.tsx +++ b/web/src/components/DisputeCard/PeriodBanner.tsx @@ -9,9 +9,9 @@ export interface IPeriodBanner { const getPeriodColors = (period: Periods, theme: Theme): [string, string] => { switch (period) { - case Periods.appeal: + case Periods.Appeal: return [theme.tint, theme.tintMedium]; - case Periods.execution: + case Periods.Execution: return [theme.secondaryPurple, theme.mediumPurple]; default: return [theme.primaryBlue, theme.mediumBlue]; @@ -56,9 +56,9 @@ const Container = styled.div>` const getPeriodLabel = (period: Periods) => { switch (period) { - case Periods.appeal: + case Periods.Appeal: return "Crowdfunding Appeal"; - case Periods.execution: + case Periods.Execution: return "Closed"; default: return "In Progress"; diff --git a/web/src/components/DisputeCard/index.tsx b/web/src/components/DisputeCard/index.tsx index e06b894f4..4cb47bfc6 100644 --- a/web/src/components/DisputeCard/index.tsx +++ b/web/src/components/DisputeCard/index.tsx @@ -1,8 +1,16 @@ import React from "react"; import styled from "styled-components"; +import { useNavigate } from "react-router-dom"; +import { utils } from "ethers"; +import Skeleton from "react-loading-skeleton"; import { Card } from "@kleros/ui-components-library"; -import PeriodBanner, { IPeriodBanner } from "./PeriodBanner"; -import DisputeInfo, { IDisputeInfo } from "./DisputeInfo"; +import { Periods } from "consts/periods"; +import { useGetMetaEvidence } from "queries/useGetMetaEvidence"; +import { useCourtPolicy } from "queries/useCourtPolicy"; +import { useIPFSQuery } from "hooks/useIPFSQuery"; +import { CasesPageQuery } from "queries/useCasesQuery"; +import PeriodBanner from "./PeriodBanner"; +import DisputeInfo from "./DisputeInfo"; const StyledCard = styled(Card)` max-width: 380px; @@ -22,26 +30,52 @@ const Container = styled.div` } `; -interface IDisputeCard extends IPeriodBanner, Omit { - title: string; -} +const getTimeLeft = ( + lastPeriodChange: string, + currentPeriodIndex: number, + timesPerPeriod: string[] +) => { + const durationCurrentPeriod = parseInt(timesPerPeriod[currentPeriodIndex]); + return parseInt(lastPeriodChange) + durationCurrentPeriod; +}; -const DisputeCard: React.FC = ({ +const DisputeCard: React.FC = ({ id, + arbitrated, period, - title, - court, - category, - rewards, - date, -}) => ( - - - -

{title}

- -
-
-); + lastPeriodChange, + subcourtID, +}) => { + const currentPeriodIndex = Periods[period]; + const rewards = `≥ ${utils.formatEther(subcourtID.feeForJuror)} ETH`; + const date = + currentPeriodIndex === 4 + ? lastPeriodChange + : getTimeLeft( + lastPeriodChange, + currentPeriodIndex, + subcourtID.timesPerPeriod + ); + const { data: metaEvidence } = useGetMetaEvidence(id, arbitrated); + const title = metaEvidence ? metaEvidence.title : ; + const { data: courtPolicyPath } = useCourtPolicy(parseInt(subcourtID.id)); + const { data: courtPolicy } = useIPFSQuery(courtPolicyPath?.args._policy); + const courtName = courtPolicy?.name; + const category = metaEvidence ? metaEvidence.category : undefined; + const navigate = useNavigate(); + return ( + navigate(`/cases/${id.toString()}`)}> + + +

{title}

+ +
+
+ ); +}; export default DisputeCard; diff --git a/web/src/components/EvidenceCard.tsx b/web/src/components/EvidenceCard.tsx new file mode 100644 index 000000000..7c1febae4 --- /dev/null +++ b/web/src/components/EvidenceCard.tsx @@ -0,0 +1,91 @@ +import React from "react"; +import styled from "styled-components"; +import Jazzicon, { jsNumberForAddress } from "react-jazzicon"; +import { Card } from "@kleros/ui-components-library"; +import AttachmentIcon from "svgs/icons/attachment.svg"; +import { useIPFSQuery } from "hooks/useIPFSQuery"; +import { shortenAddress } from "utils/shortenAddress"; + +interface IEvidenceCard { + evidence: string; + sender: string; + index: number; +} + +const EvidenceCard: React.FC = ({ evidence, sender, index }) => { + const { data } = useIPFSQuery(evidence.at(0) === "/" ? evidence : undefined); + return ( + + + #{index}: + {data ? ( + <> +

{data.name}

+

{data.description}

+ + ) : ( +

{evidence}

+ )} +
+ + +

{shortenAddress(sender)}

+ {data && ( + + + + )} +
+
+ ); +}; + +const StyledCard = styled(Card)` + width: 100%; + height: auto; +`; + +const TextContainer = styled.div` + padding: 8px; + > * { + overflow-wrap: break-word; + margin: 0; + } + > h3 { + display: inline-block; + margin: 0px 4px; + } +`; + +const Index = styled.p` + display: inline-block; +`; + +const BottomShade = styled.div` + background-color: ${({ theme }) => theme.lightBlue}; + display: flex; + align-items: center; + gap: 8px; + padding: 8px; + > * { + flex-basis: 1; + flex-shrink: 0; + margin: 0; + } +`; + +const StyledA = styled.a` + margin-left: auto; + margin-right: 8px; + display: flex; + > svg { + width: 16px; + fill: ${({ theme }) => theme.primaryBlue}; + } +`; + +export default EvidenceCard; diff --git a/web/src/components/WrongChainBoundary.tsx b/web/src/components/WrongChainBoundary.tsx index 4ba9b2471..4987705a5 100644 --- a/web/src/components/WrongChainBoundary.tsx +++ b/web/src/components/WrongChainBoundary.tsx @@ -11,6 +11,7 @@ const WrongChainRecovery: React.FC<{ resetErrorBoundary: () => void }> = ({ return (