diff --git a/.github/workflows/contracts-testing.yml b/.github/workflows/contracts-testing.yml index 72d261633..db3baa32a 100644 --- a/.github/workflows/contracts-testing.yml +++ b/.github/workflows/contracts-testing.yml @@ -38,16 +38,23 @@ jobs: registry.yarnpkg.com:443 registry.npmjs.org:443 54.185.253.63:443 - - - name: Setup Node.js environment - uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0 - with: - node-version: 18.x - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: submodules: recursive - + + - name: Set up corepack (for yarn) + run: | + corepack enable + corepack prepare yarn@4.5.1 --activate + yarn set version 4.5.1 + + - name: Setup Node.js environment + uses: actions/setup-node@39370e3970a6d050c480ffad4ff0ed4d3fdee5af # v4.1.0 + with: + node-version: 20.x + cache: yarn + - name: Cache node modules uses: actions/cache@1bd1e32a3bdc45362d1e726936510720a7c30a57 # v4.2.0 env: @@ -61,7 +68,9 @@ jobs: ${{ runner.os }}-build-${{ secrets.CACHE_VERSION }}-${{ env.cache-name }}- - name: Install contracts dependencies - run: yarn workspace @kleros/kleros-v2-contracts install + run: | + # TODO: re-enable hardened mode once the kleros-app resolution is fixed + YARN_ENABLE_HARDENED_MODE=0 yarn workspace @kleros/kleros-v2-contracts install - name: Install Foundry uses: foundry-rs/foundry-toolchain@8f1998e9878d786675189ef566a2e4bf24869773 # v1.2.0 diff --git a/.github/workflows/pr-labels.yml b/.github/workflows/pr-labels.yml deleted file mode 100644 index 20f4fe3c8..000000000 --- a/.github/workflows/pr-labels.yml +++ /dev/null @@ -1,39 +0,0 @@ -name: Add PR labels - -on: - pull_request: - types: [opened, edited] - branches-ignore: - - 'dependabot/**' - - 'renovate/**' - -permissions: # added using https://github.com/step-security/secure-workflows - contents: read - issues: read - pull-requests: write - -jobs: - copy-labels: - runs-on: ubuntu-latest - name: Copy labels from linked issues - steps: - - name: Harden Runner - uses: step-security/harden-runner@1b05615854632b887b69ae1be8cbefe72d3ae423 # v2.5.0 - with: - disable-sudo: true - egress-policy: block - allowed-endpoints: > - yarnpkg.com:443 - github.com:443 - nightly.yarnpkg.com:443 - nodejs.org:443 - objects.githubusercontent.com:443 - registry.yarnpkg.com:443 - registry.npmjs.org:443 - 54.185.253.63:443 - - - name: copy-labels - uses: michalvankodev/copy-issue-labels@f54e957e58fc976eba5ffa36e1a1030572dbb78d # v1.3.0 - with: - repo-token: ${{ secrets.GITHUB_TOKEN }} - diff --git a/kleros-app/package.json b/kleros-app/package.json index 43ddf0468..11893c4e9 100644 --- a/kleros-app/package.json +++ b/kleros-app/package.json @@ -61,6 +61,6 @@ "react": "^18.3.1", "react-dom": "^18.3.1", "viem": "^2.21.42", - "wagmi": "^2.13.0" + "wagmi": "^2.13.5" } } diff --git a/package.json b/package.json index 6550c5e39..00787c659 100644 --- a/package.json +++ b/package.json @@ -81,7 +81,9 @@ "local-stack": "scripts/tmux-local-stack.sh", "changelog": "conventional-changelog --infile CHANGELOG.md --same-file --release-count 0 && prettier --write CHANGELOG.md", "postinstall": "yarn check-prerequisites; husky install", - "reinstall": "YARN_CHECKSUM_BEHAVIOR=update yarn install --no-immutable" + "reinstall": "YARN_CHECKSUM_BEHAVIOR=update yarn install --no-immutable", + "build:web:ci": "yarn workspaces focus @kleros/kleros-v2-web && yarn workspaces foreach -Ap --include kleros-app --include contracts run build && yarn workspace @kleros/kleros-v2-web build-netlify", + "build:web-devtools:ci": "yarn workspaces focus @kleros/kleros-v2-web-devtools && yarn workspaces foreach -Ap --include contracts run build && yarn workspace @kleros/kleros-v2-web-devtools build-netlify" }, "alias": { "process": "process/browser.js", diff --git a/subgraph/core-neo/subgraph.yaml b/subgraph/core-neo/subgraph.yaml index 7ea2979cc..37feb1f7d 100644 --- a/subgraph/core-neo/subgraph.yaml +++ b/subgraph/core-neo/subgraph.yaml @@ -8,9 +8,9 @@ dataSources: name: KlerosCore network: arbitrum-one source: - address: "0xCd415C03dfa85B02646C7e2977F22a480c4354F1" + address: "0x991d2df165670b9cac3B022f4B68D65b664222ea" abi: KlerosCore - startBlock: 190274596 + startBlock: 272063254 mapping: kind: ethereum/events apiVersion: 0.0.7 @@ -64,9 +64,9 @@ dataSources: name: PolicyRegistry network: arbitrum-one source: - address: "0x26c1980120F1C82cF611D666CE81D2b54d018547" + address: "0x553dcbF6aB3aE06a1064b5200Df1B5A9fB403d3c" abi: PolicyRegistry - startBlock: 190274403 + startBlock: 272063037 mapping: kind: ethereum/events apiVersion: 0.0.7 @@ -84,9 +84,9 @@ dataSources: name: DisputeKitClassic network: arbitrum-one source: - address: "0xb7c292cD9Fd3d20De84a71AE1caF054eEB6374A9" + address: "0x70B464be85A547144C72485eBa2577E5D3A45421" abi: DisputeKitClassic - startBlock: 190274518 + startBlock: 272063168 mapping: kind: ethereum/events apiVersion: 0.0.7 @@ -119,9 +119,9 @@ dataSources: name: EvidenceModule network: arbitrum-one source: - address: "0xe62B776498F48061ef9425fCEf30F3d1370DB005" + address: "0x48e052B4A6dC4F30e90930F1CeaAFd83b3981EB3" abi: EvidenceModule - startBlock: 190274441 + startBlock: 272063086 mapping: kind: ethereum/events apiVersion: 0.0.7 @@ -140,9 +140,9 @@ dataSources: name: SortitionModule network: arbitrum-one source: - address: "0x614498118850184c62f82d08261109334bFB050f" + address: "0x21A9402aDb818744B296e1d1BE58C804118DC03D" abi: SortitionModule - startBlock: 190274557 + startBlock: 272063201 mapping: kind: ethereum/events apiVersion: 0.0.7 diff --git a/subgraph/core/schema.graphql b/subgraph/core/schema.graphql index e2700a5f3..dc34b33b3 100644 --- a/subgraph/core/schema.graphql +++ b/subgraph/core/schema.graphql @@ -160,6 +160,7 @@ type Dispute @entity { disputeID: BigInt! court: Court! createdAt: BigInt + transactionHash: String! arbitrated: Arbitrable! period: Period! ruled: Boolean! @@ -180,6 +181,8 @@ type Dispute @entity { arbitrableChainId:BigInt externalDisputeId:BigInt templateId:BigInt + rulingTimestamp:BigInt + rulingTransactionHash:String } type PeriodIndexCounter @entity { @@ -303,6 +306,8 @@ type ClassicJustification @entity { choice: BigInt! votes: [ClassicVote!]! @derivedFrom(field: "justification") reference: String! + transactionHash: String! + timestamp: BigInt! } type ClassicEvidenceGroup implements EvidenceGroup @entity { diff --git a/subgraph/core/src/DisputeKitClassic.ts b/subgraph/core/src/DisputeKitClassic.ts index 4c8c37729..a8b9b3361 100644 --- a/subgraph/core/src/DisputeKitClassic.ts +++ b/subgraph/core/src/DisputeKitClassic.ts @@ -66,6 +66,8 @@ export function handleVoteCast(event: VoteCast): void { justification.localRound = currentLocalRoundID; justification.choice = choice; justification.reference = event.params._justification; + justification.transactionHash = event.transaction.hash.toHexString(); + justification.timestamp = event.block.timestamp; justification.save(); const currentRulingInfo = updateCountsAndGetCurrentRuling( currentLocalRoundID, diff --git a/subgraph/core/src/KlerosCore.ts b/subgraph/core/src/KlerosCore.ts index 8f968f83d..e6c06a354 100644 --- a/subgraph/core/src/KlerosCore.ts +++ b/subgraph/core/src/KlerosCore.ts @@ -184,6 +184,8 @@ export function handleRuling(event: Ruling): void { const dispute = Dispute.load(disputeID.toString()); if (!dispute) return; dispute.ruled = true; + dispute.rulingTransactionHash = event.transaction.hash.toHexString(); + dispute.rulingTimestamp = event.block.timestamp; dispute.save(); const court = Court.load(dispute.court); if (!court) return; diff --git a/subgraph/core/src/entities/Dispute.ts b/subgraph/core/src/entities/Dispute.ts index 49bea55ad..3e03eb1f8 100644 --- a/subgraph/core/src/entities/Dispute.ts +++ b/subgraph/core/src/entities/Dispute.ts @@ -21,6 +21,7 @@ export function createDisputeFromEvent(event: DisputeCreation): void { dispute.lastPeriodChange = event.block.timestamp; dispute.lastPeriodChangeBlockNumber = event.block.number; dispute.periodNotificationIndex = getAndIncrementPeriodCounter(dispute.period); + dispute.transactionHash = event.transaction.hash.toHexString(); const court = Court.load(courtID); if (!court) return; dispute.periodDeadline = event.block.timestamp.plus(court.timesPerPeriod[0]); diff --git a/subgraph/package.json b/subgraph/package.json index 21c2d258a..c6f1294ad 100644 --- a/subgraph/package.json +++ b/subgraph/package.json @@ -1,6 +1,6 @@ { "name": "@kleros/kleros-v2-subgraph", - "version": "0.9.1", + "version": "0.10.1", "license": "MIT", "scripts": { "update:core:arbitrum-sepolia-devnet": "./scripts/update.sh arbitrumSepoliaDevnet arbitrum-sepolia core/subgraph.yaml", diff --git a/web-devtools/package.json b/web-devtools/package.json index 33d2c4bcb..3d337091d 100644 --- a/web-devtools/package.json +++ b/web-devtools/package.json @@ -48,7 +48,8 @@ }, "dependencies": { "@kleros/kleros-sdk": "workspace:^", - "@kleros/ui-components-library": "^2.19.0", + "@kleros/kleros-v2-contracts": "workspace:^", + "@kleros/ui-components-library": "^2.20.0", "@tanstack/react-query": "^5.61.0", "@wagmi/connectors": "^5.5.0", "@wagmi/core": "^2.15.0", @@ -65,6 +66,6 @@ "typewriter-effect": "^2.21.0", "vanilla-jsoneditor": "^0.21.6", "viem": "^2.21.50", - "wagmi": "^2.13.0" + "wagmi": "^2.13.5" } } diff --git a/web/package.json b/web/package.json index 80f81b0cf..efe863eb5 100644 --- a/web/package.json +++ b/web/package.json @@ -78,10 +78,10 @@ }, "dependencies": { "@cyntler/react-doc-viewer": "^1.17.0", - "@kleros/kleros-app": "^2.0.2", + "@kleros/kleros-app": "workspace:^", "@kleros/kleros-sdk": "workspace:^", "@kleros/kleros-v2-contracts": "workspace:^", - "@kleros/ui-components-library": "^2.19.0", + "@kleros/ui-components-library": "^2.20.0", "@lifi/wallet-management": "^3.4.6", "@lifi/widget": "^3.12.3", "@sentry/react": "^7.120.0", @@ -117,6 +117,7 @@ "react-toastify": "^9.1.3", "react-use": "^17.5.1", "styled-components": "^5.3.3", + "subgraph-status": "^1.2.3", "viem": "^2.21.54", "wagmi": "^2.13.5" } diff --git a/web/src/components/CasesDisplay/Filters.tsx b/web/src/components/CasesDisplay/Filters.tsx index cb417ac65..a8000de1e 100644 --- a/web/src/components/CasesDisplay/Filters.tsx +++ b/web/src/components/CasesDisplay/Filters.tsx @@ -3,7 +3,7 @@ import styled, { css, useTheme } from "styled-components"; import { hoverShortTransitionTiming } from "styles/commonStyles"; -import { useNavigate, useParams } from "react-router-dom"; +import { useNavigate, useParams, useSearchParams } from "react-router-dom"; import { DropdownSelect } from "@kleros/ui-components-library"; @@ -55,16 +55,17 @@ const Filters: React.FC = () => { const { ruled, period, ...filterObject } = decodeURIFilter(filter ?? "all"); const navigate = useNavigate(); const location = useRootPath(); + const [searchParams] = useSearchParams(); const handleStatusChange = (value: string | number) => { const parsedValue = JSON.parse(value as string); const encodedFilter = encodeURIFilter({ ...filterObject, ...parsedValue }); - navigate(`${location}/1/${order}/${encodedFilter}`); + navigate(`${location}/1/${order}/${encodedFilter}?${searchParams.toString()}`); }; const handleOrderChange = (value: string | number) => { const encodedFilter = encodeURIFilter({ ruled, period, ...filterObject }); - navigate(`${location}/1/${value}/${encodedFilter}`); + navigate(`${location}/1/${value}/${encodedFilter}?${searchParams.toString()}`); }; const { isList, setIsList } = useIsList(); diff --git a/web/src/components/CasesDisplay/Search.tsx b/web/src/components/CasesDisplay/Search.tsx index 2179df17f..6a4e9f69d 100644 --- a/web/src/components/CasesDisplay/Search.tsx +++ b/web/src/components/CasesDisplay/Search.tsx @@ -1,10 +1,9 @@ -import React, { useMemo, useState } from "react"; +import React, { useMemo, useRef, useState } from "react"; import styled, { css } from "styled-components"; import Skeleton from "react-loading-skeleton"; -import { useNavigate, useParams } from "react-router-dom"; +import { useNavigate, useParams, useSearchParams } from "react-router-dom"; import { useDebounce } from "react-use"; - import { Searchbar, DropdownCascader } from "@kleros/ui-components-library"; import { isEmpty, isUndefined } from "utils/index"; @@ -39,6 +38,7 @@ const SearchBarContainer = styled.div` const StyledSearchbar = styled(Searchbar)` flex: 1; flex-basis: 310px; + input { font-size: 16px; height: 45px; @@ -53,16 +53,25 @@ const Search: React.FC = () => { const decodedFilter = decodeURIFilter(filter ?? "all"); const { id: searchValue, ...filterObject } = decodedFilter; const [search, setSearch] = useState(searchValue ?? ""); + const initialRenderRef = useRef(true); const navigate = useNavigate(); + const [searchParams] = useSearchParams(); + useDebounce( () => { + if (initialRenderRef.current && isEmpty(search)) { + initialRenderRef.current = false; + return; + } + initialRenderRef.current = false; const newFilters = isEmpty(search) ? { ...filterObject } : { ...filterObject, id: search }; const encodedFilter = encodeURIFilter(newFilters); - navigate(`${location}/${page}/${order}/${encodedFilter}`); + navigate(`${location}/${page}/${order}/${encodedFilter}?${searchParams.toString()}`); }, 500, [search] ); + const { data: courtTreeData } = useCourtTree(); const items = useMemo(() => { if (!isUndefined(courtTreeData)) { @@ -82,7 +91,7 @@ const Search: React.FC = () => { onSelect={(value) => { const { court: _, ...filterWithoutCourt } = decodedFilter; const newFilter = value === "all" ? filterWithoutCourt : { ...decodedFilter, court: value.toString() }; - navigate(`${location}/${page}/${order}/${encodeURIFilter(newFilter)}`); + navigate(`${location}/${page}/${order}/${encodeURIFilter(newFilter)}?${searchParams.toString()}`); }} /> ) : ( @@ -90,6 +99,7 @@ const Search: React.FC = () => { )} = ({ name, address }) => { const resolvedAddress = addressFromENS ?? (address as `0x${string}`); return ( - + {isLoading ? : } {isLoading ? : }  diff --git a/web/src/components/DisputePreview/DisputeContext.tsx b/web/src/components/DisputePreview/DisputeContext.tsx index 6a93a81ce..12cbb673b 100644 --- a/web/src/components/DisputePreview/DisputeContext.tsx +++ b/web/src/components/DisputePreview/DisputeContext.tsx @@ -12,10 +12,11 @@ import { responsiveSize } from "styles/responsiveSize"; import ReactMarkdown from "components/ReactMarkdown"; import { StyledSkeleton } from "components/StyledSkeleton"; -import AliasDisplay from "./Alias"; import { Divider } from "../Divider"; import { ExternalLink } from "../ExternalLink"; +import AliasDisplay from "./Alias"; + const StyledH1 = styled.h1` margin: 0; word-wrap: break-word; @@ -75,16 +76,18 @@ export const DisputeContext: React.FC = ({ disputeDetails, isRp return ( <> - {isUndefined(disputeDetails) ? : (disputeDetails?.title ?? errMsg)} + + {isUndefined(disputeDetails) ? : (disputeDetails?.title ?? errMsg)} + {disputeDetails?.question?.trim() || disputeDetails?.description?.trim() ? (
{disputeDetails?.question?.trim() ? ( - + {disputeDetails.question} ) : null} {disputeDetails?.description?.trim() ? ( - + {disputeDetails.description} ) : null} @@ -100,7 +103,7 @@ export const DisputeContext: React.FC = ({ disputeDetails, isRp {isUndefined(disputeDetails) ? null : Voting Options} {disputeDetails?.answers?.map((answer: IAnswer, i: number) => ( - + {answer.title} {answer.description.trim() ? ` - ${answer.description}` : null} diff --git a/web/src/components/DisputeView/DisputeCardView.tsx b/web/src/components/DisputeView/DisputeCardView.tsx index 3cb231c5a..7cf554918 100644 --- a/web/src/components/DisputeView/DisputeCardView.tsx +++ b/web/src/components/DisputeView/DisputeCardView.tsx @@ -46,7 +46,7 @@ const StyledCaseCardTitleSkeleton = styled(StyledSkeleton)` const TruncatedTitle = ({ text, maxLength }) => { const truncatedText = text.length <= maxLength ? text : text.slice(0, maxLength) + "…"; - return {truncatedText}; + return {truncatedText}; }; interface IDisputeCardView { diff --git a/web/src/components/DisputeView/DisputeListView.tsx b/web/src/components/DisputeView/DisputeListView.tsx index d0c1c967a..5b00b7967 100644 --- a/web/src/components/DisputeView/DisputeListView.tsx +++ b/web/src/components/DisputeView/DisputeListView.tsx @@ -8,8 +8,8 @@ import { Card } from "@kleros/ui-components-library"; import { Periods } from "consts/periods"; -import { responsiveSize } from "styles/responsiveSize"; import { hoverShortTransitionTiming } from "styles/commonStyles"; +import { responsiveSize } from "styles/responsiveSize"; import DisputeInfo from "./DisputeInfo"; import PeriodBanner from "./PeriodBanner"; @@ -37,11 +37,12 @@ const TitleContainer = styled.div<{ isLabel?: boolean }>` width: ${({ isLabel }) => (isLabel ? responsiveSize(150, 340, 900) : "fit-content")}; h3 { margin: 0; + flex: 1; } `; const TruncatedTitle = ({ text, maxLength }) => { const truncatedText = text.length <= maxLength ? text : text.slice(0, maxLength) + "…"; - return

{truncatedText}

; + return

{truncatedText}

; }; interface IDisputeListView { title: string; diff --git a/web/src/components/EvidenceCard.tsx b/web/src/components/EvidenceCard.tsx index 0a328b29d..3833316dc 100644 --- a/web/src/components/EvidenceCard.tsx +++ b/web/src/components/EvidenceCard.tsx @@ -1,10 +1,6 @@ import React, { useMemo } from "react"; import styled, { css } from "styled-components"; -import { landscapeStyle } from "styles/landscapeStyle"; -import { responsiveSize } from "styles/responsiveSize"; -import { hoverShortTransitionTiming } from "styles/commonStyles"; - import Identicon from "react-identicons"; import ReactMarkdown from "react-markdown"; @@ -18,6 +14,11 @@ import { getIpfsUrl } from "utils/getIpfsUrl"; import { shortenAddress } from "utils/shortenAddress"; import { type Evidence } from "src/graphql/graphql"; +import { getTxnExplorerLink } from "src/utils"; + +import { hoverShortTransitionTiming } from "styles/commonStyles"; +import { landscapeStyle } from "styles/landscapeStyle"; +import { responsiveSize } from "styles/responsiveSize"; import { ExternalLink } from "./ExternalLink"; import { InternalLink } from "./InternalLink"; @@ -65,6 +66,7 @@ const Index = styled.p` color: ${({ theme }) => theme.secondaryText}; `; +const ReactMarkdownWrapper = styled.div``; const StyledReactMarkdown = styled(ReactMarkdown)` a { font-size: 16px; @@ -137,7 +139,7 @@ const AccountContainer = styled.div` } `; -const HoverStyle = css` +const ExternalLinkHoverStyle = css` :hover { text-decoration: underline; color: ${({ theme }) => theme.primaryBlue}; @@ -153,12 +155,15 @@ const HoverStyle = css` `; const Address = styled.p` - ${HoverStyle} margin: 0; + + :hover { + color: ${({ theme }) => theme.secondaryBlue}; + } `; const StyledExternalLink = styled(ExternalLink)` - ${HoverStyle} + ${ExternalLinkHoverStyle} `; const DesktopText = styled.span` @@ -219,30 +224,34 @@ const EvidenceCard: React.FC = ({ description, fileURI, }) => { - const addressExplorerLink = useMemo(() => { - return `${getChain(DEFAULT_CHAIN)?.blockExplorers?.default.url}/address/${sender}`; - }, [sender]); + const dashboardLink = `/dashboard/1/desc/all?address=${sender}`; const transactionExplorerLink = useMemo(() => { - return `${getChain(DEFAULT_CHAIN)?.blockExplorers?.default.url}/tx/${transactionHash}`; + return getTxnExplorerLink(transactionHash ?? ""); }, [transactionHash]); return ( - + #{index}.

{name}

- {name && description ? {description} :

{evidence}

} + {name && description ? ( + + {description} + + ) : ( +

{evidence}

+ )}
- +
{shortenAddress(sender)}
-
+
diff --git a/web/src/components/Field.tsx b/web/src/components/Field.tsx index e9d5bff65..891ae8936 100644 --- a/web/src/components/Field.tsx +++ b/web/src/components/Field.tsx @@ -1,5 +1,6 @@ import React from "react"; import styled, { css } from "styled-components"; + import { landscapeStyle } from "styles/landscapeStyle"; import { InternalLink } from "./InternalLink"; @@ -99,7 +100,7 @@ const Field: React.FC = ({ className, }) => { return ( - + {(!displayAsList || isOverview || isJurorBalance) && } {link ? ( diff --git a/web/src/components/LabeledInput.tsx b/web/src/components/LabeledInput.tsx index dee85ebbc..5a8e1332e 100644 --- a/web/src/components/LabeledInput.tsx +++ b/web/src/components/LabeledInput.tsx @@ -30,7 +30,7 @@ const LabeledInput: React.FC = (props) => { return ( {!isUndefined(props.label) ? {props.label} : null} - + ); }; diff --git a/web/src/components/Popup/MiniGuides/PageContentsTemplate.tsx b/web/src/components/Popup/MiniGuides/PageContentsTemplate.tsx index d8b4e9741..893f0f109 100644 --- a/web/src/components/Popup/MiniGuides/PageContentsTemplate.tsx +++ b/web/src/components/Popup/MiniGuides/PageContentsTemplate.tsx @@ -24,6 +24,7 @@ export const LeftContentContainer = styled.div` export const StyledImage = styled.div` width: ${responsiveSize(260, 460)}; + ${landscapeStyle( () => css` width: 389px; diff --git a/web/src/components/TxnHash.tsx b/web/src/components/TxnHash.tsx index bb25ce7c1..41e35f4d9 100644 --- a/web/src/components/TxnHash.tsx +++ b/web/src/components/TxnHash.tsx @@ -3,7 +3,7 @@ import styled from "styled-components"; import NewTabIcon from "svgs/icons/new-tab.svg"; -import { DEFAULT_CHAIN, getChain } from "consts/chains"; +import { getTxnExplorerLink } from "src/utils"; import { ExternalLink } from "./ExternalLink"; @@ -23,7 +23,7 @@ interface ITxnHash { } const TxnHash: React.FC = ({ hash, variant }) => { const transactionExplorerLink = useMemo(() => { - return `${getChain(DEFAULT_CHAIN)?.blockExplorers?.default.url}/tx/${hash}`; + return getTxnExplorerLink(hash); }, [hash]); return ( diff --git a/web/src/components/Verdict/Answer.tsx b/web/src/components/Verdict/Answer.tsx index b52e6759f..fe03b6b66 100644 --- a/web/src/components/Verdict/Answer.tsx +++ b/web/src/components/Verdict/Answer.tsx @@ -1,17 +1,8 @@ import React from "react"; -import styled from "styled-components"; import { Answer } from "@kleros/kleros-sdk/src/dataMappings/utils/disputeDetailsTypes"; -import { AnswerDescription, AnswerTitle, AnswerTitleAndDescription } from "../DisputePreview/DisputeContext"; - -const Container = styled.div` - display: flex; - flex-direction: row; - flex-wrap: wrap; - align-items: center; - gap: 6px; -`; +import { AnswerTitle, AnswerTitleAndDescription } from "../DisputePreview/DisputeContext"; interface IAnswer { answer?: Answer; @@ -22,14 +13,13 @@ const AnswerDisplay: React.FC = ({ answer, currentRuling }) => { return ( <> {answer ? ( - + {answer.title} - {answer.description.trim() ? ` - ${answer.description}` : null} ) : ( - + {currentRuling !== 0 ? Answer 0x{currentRuling} : Refuse to Arbitrate} - + )} ); diff --git a/web/src/components/Verdict/DisputeTimeline.tsx b/web/src/components/Verdict/DisputeTimeline.tsx index a120bf9fa..0412bbb0d 100644 --- a/web/src/components/Verdict/DisputeTimeline.tsx +++ b/web/src/components/Verdict/DisputeTimeline.tsx @@ -1,14 +1,14 @@ import React, { useMemo } from "react"; import styled, { useTheme } from "styled-components"; -import { responsiveSize } from "styles/responsiveSize"; - +import Skeleton from "react-loading-skeleton"; import { useParams } from "react-router-dom"; import { _TimelineItem1, CustomTimeline } from "@kleros/ui-components-library"; import CalendarIcon from "svgs/icons/calendar.svg"; import ClosedCaseIcon from "svgs/icons/check-circle-outline.svg"; +import NewTabIcon from "svgs/icons/new-tab.svg"; import { Periods } from "consts/periods"; import { usePopulatedDisputeData } from "hooks/queries/usePopulatedDisputeData"; @@ -19,9 +19,14 @@ import { DisputeDetailsQuery, useDisputeDetailsQuery } from "queries/useDisputeD import { useVotingHistory } from "queries/useVotingHistory"; import { ClassicRound } from "src/graphql/graphql"; +import { getTxnExplorerLink } from "src/utils"; + +import { responsiveSize } from "styles/responsiveSize"; import { StyledClosedCircle } from "components/StyledIcons/ClosedCircleIcon"; +import { ExternalLink } from "../ExternalLink"; + const Container = styled.div` display: flex; position: relative; @@ -50,6 +55,18 @@ const StyledCalendarIcon = styled(CalendarIcon)` height: 14px; `; +const StyledNewTabIcon = styled(NewTabIcon)` + margin-bottom: 2px; + path { + fill: ${({ theme }) => theme.primaryBlue}; + } + :hover { + path { + fill: ${({ theme }) => theme.secondaryBlue}; + } + } +`; + const formatDate = (date: string) => { const options: Intl.DateTimeFormatOptions = { year: "numeric", month: "long", day: "numeric" }; const startingDate = new Date(parseInt(date) * 1000); @@ -67,6 +84,9 @@ const useItems = (disputeDetails?: DisputeDetailsQuery, arbitrable?: `0x${string const localRounds: ClassicRound[] = getLocalRounds(votingHistory?.dispute?.disputeKitDispute) as ClassicRound[]; const rounds = votingHistory?.dispute?.rounds; const theme = useTheme(); + const txnExplorerLink = useMemo(() => { + return getTxnExplorerLink(votingHistory?.dispute?.transactionHash ?? ""); + }, [votingHistory]); return useMemo(() => { const dispute = disputeDetails?.dispute; @@ -119,7 +139,11 @@ const useItems = (disputeDetails?: DisputeDetailsQuery, arbitrable?: `0x${string [ { title: "Dispute created", - party: "", + party: ( + + + + ), subtitle: formatDate(votingHistory?.dispute?.createdAt), rightSided: true, variant: theme.secondaryPurple, @@ -128,7 +152,7 @@ const useItems = (disputeDetails?: DisputeDetailsQuery, arbitrable?: `0x${string ); } return; - }, [disputeDetails, disputeData, localRounds, theme]); + }, [disputeDetails, disputeData, localRounds, theme, rounds, votingHistory, txnExplorerLink]); }; interface IDisputeTimeline { @@ -138,15 +162,30 @@ interface IDisputeTimeline { const DisputeTimeline: React.FC = ({ arbitrable }) => { const { id } = useParams(); const { data: disputeDetails } = useDisputeDetailsQuery(id); + const { data: votingHistory } = useVotingHistory(id); const items = useItems(disputeDetails, arbitrable); + const transactionExplorerLink = useMemo(() => { + return getTxnExplorerLink(disputeDetails?.dispute?.rulingTransactionHash ?? ""); + }, [disputeDetails]); + return ( {items && } - {disputeDetails?.dispute?.ruled && items && ( + {disputeDetails?.dispute?.ruled && ( - Enforcement: {items.at(-1)?.subtitle} + + Enforcement:{" "} + {disputeDetails.dispute.rulingTimestamp ? ( + + {formatDate(disputeDetails.dispute.rulingTimestamp)} + + ) : ( + + )}{" "} + / {votingHistory?.dispute?.rounds.at(-1)?.court.name} + )} diff --git a/web/src/components/Verdict/FinalDecision.tsx b/web/src/components/Verdict/FinalDecision.tsx index 40729df8e..3191c53c8 100644 --- a/web/src/components/Verdict/FinalDecision.tsx +++ b/web/src/components/Verdict/FinalDecision.tsx @@ -7,9 +7,9 @@ import { useAccount } from "wagmi"; import ArrowIcon from "svgs/icons/arrow.svg"; +import { DEFAULT_CHAIN } from "consts/chains"; import { REFETCH_INTERVAL } from "consts/index"; import { Periods } from "consts/periods"; -import { DEFAULT_CHAIN } from "consts/chains"; import { useReadKlerosCoreCurrentRuling } from "hooks/contracts/generated"; import { usePopulatedDisputeData } from "hooks/queries/usePopulatedDisputeData"; import { useVotingHistory } from "hooks/queries/useVotingHistory"; @@ -21,26 +21,30 @@ import { useDisputeDetailsQuery } from "queries/useDisputeDetailsQuery"; import { landscapeStyle } from "styles/landscapeStyle"; -import RulingAndRewardsIndicators from "./RulingAndRewardsIndicators"; -import AnswerDisplay from "./Answer"; import { Divider } from "../Divider"; import { StyledArrowLink } from "../StyledArrowLink"; +import AnswerDisplay from "./Answer"; +import RulingAndRewardsIndicators from "./RulingAndRewardsIndicators"; + const Container = styled.div` width: 100%; `; const JuryContainer = styled.div` display: flex; - flex-direction: row; - flex-wrap: wrap; align-items: center; gap: 5px 7px; + flex-wrap: wrap; h3 { line-height: 21px; margin-bottom: 0px; } + + > div { + flex: 1; + } `; const VerdictContainer = styled.div` @@ -104,7 +108,7 @@ const FinalDecision: React.FC = ({ arbitrable }) => { if (isVotingPeriod && isHiddenVotes && commited && !hasVoted) return "Reveal your vote"; if (isVotingPeriod && !isHiddenVotes && !hasVoted) return "Cast your vote"; return "Check how the jury voted"; - }, [wasDrawn, hasVoted, isCommitPeriod, isVotingPeriod, commited, isHiddenVotes]); + }, [wasDrawn, hasVoted, isCommitPeriod, isVotingPeriod, commited, isHiddenVotes, isDisconnected]); return ( diff --git a/web/src/hooks/queries/useDisputeDetailsQuery.ts b/web/src/hooks/queries/useDisputeDetailsQuery.ts index c5b359d08..71a417904 100644 --- a/web/src/hooks/queries/useDisputeDetailsQuery.ts +++ b/web/src/hooks/queries/useDisputeDetailsQuery.ts @@ -34,6 +34,8 @@ const disputeDetailsQuery = graphql(` arbitrableChainId externalDisputeId templateId + rulingTimestamp + rulingTransactionHash } } `); diff --git a/web/src/hooks/queries/useVotingHistory.ts b/web/src/hooks/queries/useVotingHistory.ts index 4991412dd..e1210ddaa 100644 --- a/web/src/hooks/queries/useVotingHistory.ts +++ b/web/src/hooks/queries/useVotingHistory.ts @@ -12,6 +12,7 @@ const votingHistoryQuery = graphql(` dispute(id: $disputeID) { id createdAt + transactionHash ruled rounds { nbVotes @@ -29,6 +30,8 @@ const votingHistoryQuery = graphql(` ... on ClassicVote { commited justification { + transactionHash + timestamp choice reference } diff --git a/web/src/hooks/useCoinPrice.tsx b/web/src/hooks/useCoinPrice.tsx index edd27e2e9..1fc6aff61 100644 --- a/web/src/hooks/useCoinPrice.tsx +++ b/web/src/hooks/useCoinPrice.tsx @@ -6,10 +6,14 @@ const fetchCoinPrices = async (...coinIds) => { return data.coins; }; +export type Prices = { + [coinId: string]: { price: number }; +}; + export const useCoinPrice = (coinIds: string[]) => { const isEnabled = coinIds !== undefined; - const { data: prices, isError } = useQuery({ + const { data: prices, isError } = useQuery({ queryKey: [`coinPrice${coinIds}`], enabled: isEnabled, queryFn: async () => fetchCoinPrices(coinIds), diff --git a/web/src/layout/Header/index.tsx b/web/src/layout/Header/index.tsx index cf319f5b1..f7e728cdc 100644 --- a/web/src/layout/Header/index.tsx +++ b/web/src/layout/Header/index.tsx @@ -1,5 +1,9 @@ import React from "react"; -import styled from "styled-components"; +import styled, { useTheme } from "styled-components"; + +import { StatusBanner } from "subgraph-status"; + +import { getGraphqlUrl } from "utils/getGraphqlUrl"; import DesktopHeader from "./DesktopHeader"; import MobileHeader from "./MobileHeader"; @@ -8,7 +12,6 @@ const Container = styled.div` display: flex; flex-wrap: wrap; position: sticky; - padding: 0 24px; z-index: 10; top: 0; width: 100%; @@ -17,11 +20,45 @@ const Container = styled.div` -webkit-backdrop-filter: ${({ theme }) => (theme.name === "dark" ? "blur(12px)" : "none")}; // Safari support `; +const HeaderContainer = styled.div` + width: 100%; + padding: 0px 24px; +`; + +const StyledBanner = styled(StatusBanner)` + position: sticky !important; + .status-text { + h2 { + margin: 0; + line-height: 24px; + } + } +`; + const Header: React.FC = () => { + const theme = useTheme(); + return ( - - + + + + + ); }; diff --git a/web/src/layout/Header/navbar/Menu/Settings/Notifications/FormContactDetails/FormContact.tsx b/web/src/layout/Header/navbar/Menu/Settings/Notifications/FormContactDetails/FormContact.tsx index 073f53300..e317b2770 100644 --- a/web/src/layout/Header/navbar/Menu/Settings/Notifications/FormContactDetails/FormContact.tsx +++ b/web/src/layout/Header/navbar/Menu/Settings/Notifications/FormContactDetails/FormContact.tsx @@ -2,6 +2,7 @@ import React, { Dispatch, SetStateAction, useMemo, useEffect } from "react"; import styled from "styled-components"; import { Field } from "@kleros/ui-components-library"; + import { isEmpty } from "src/utils"; const StyledLabel = styled.label` @@ -58,6 +59,7 @@ const FormContact: React.FC = ({ <> {contactLabel} = ({ isAppealMiniGuideOpen, toggle const options = useOptionsContext(); const { winningChoice, paidFees, fundedChoices } = useFundingContext(); - return ( + return options && options.length > 2 ? (
Appeal Results - Last Round @@ -37,24 +38,20 @@ const AppealHistory: React.FC = ({ isAppealMiniGuideOpen, toggle /> - {options ? ( - options.map((option, index) => { - return ( - - ); - }) - ) : ( -

Not in appeal period

- )} + {options.map((option, index) => ( + + ))}
+ ) : ( + ); }; export default AppealHistory; diff --git a/web/src/pages/Cases/CaseDetails/Evidence/EvidenceSearch.tsx b/web/src/pages/Cases/CaseDetails/Evidence/EvidenceSearch.tsx index 8d42dfebe..60a78da4d 100644 --- a/web/src/pages/Cases/CaseDetails/Evidence/EvidenceSearch.tsx +++ b/web/src/pages/Cases/CaseDetails/Evidence/EvidenceSearch.tsx @@ -48,6 +48,7 @@ const EvidenceSearch: React.FC = ({ search, setSearch, evidence setSearch(e.target.value)} value={search} diff --git a/web/src/pages/Cases/CaseDetails/Evidence/SubmitEvidenceModal.tsx b/web/src/pages/Cases/CaseDetails/Evidence/SubmitEvidenceModal.tsx index a577af8f2..d8056574a 100644 --- a/web/src/pages/Cases/CaseDetails/Evidence/SubmitEvidenceModal.tsx +++ b/web/src/pages/Cases/CaseDetails/Evidence/SubmitEvidenceModal.tsx @@ -4,15 +4,16 @@ import styled from "styled-components"; import Modal from "react-modal"; import { useWalletClient, usePublicClient, useConfig } from "wagmi"; +import { useAtlasProvider, Roles } from "@kleros/kleros-app"; import { Textarea, Button, FileUploader } from "@kleros/ui-components-library"; -import { useAtlasProvider, Roles } from "@kleros/kleros-app"; import { simulateEvidenceModuleSubmitEvidence } from "hooks/contracts/generated"; import { wrapWithToast, errorToast, infoToast, successToast } from "utils/wrapWithToast"; +import { isEmpty } from "src/utils"; + import EnsureAuth from "components/EnsureAuth"; import { EnsureChain } from "components/EnsureChain"; -import { isEmpty } from "src/utils"; const StyledModal = styled(Modal)` position: absolute; @@ -91,7 +92,12 @@ const SubmitEvidenceModal: React.FC<{ return (

Submit New Evidence

- setMessage(e.target.value)} placeholder="Your Arguments" /> + setMessage(e.target.value)} + placeholder="Your Arguments" + /> setFile(file)} />