diff --git a/.github/workflows/deploy-subgraph.yml b/.github/workflows/deploy-subgraph.yml index 6bf3cd87b..112031ecc 100644 --- a/.github/workflows/deploy-subgraph.yml +++ b/.github/workflows/deploy-subgraph.yml @@ -9,6 +9,7 @@ on: default: 'arbitrum-goerli' type: choice options: + - arbitrum-goerli-devnet - arbitrum-goerli - arbitrum update: diff --git a/contracts/package.json b/contracts/package.json index b19dc857b..fec51ef60 100644 --- a/contracts/package.json +++ b/contracts/package.json @@ -75,7 +75,7 @@ "pino-pretty": "^10.0.0", "shelljs": "^0.8.5", "solhint-plugin-prettier": "^0.0.5", - "solidity-coverage": "0.8.2", + "solidity-coverage": "0.8.5", "ts-node": "^10.9.1", "typechain": "^8.3.1", "typescript": "^4.9.5" diff --git a/subgraph/package.json b/subgraph/package.json index 5b00b41f5..dcb15a5df 100644 --- a/subgraph/package.json +++ b/subgraph/package.json @@ -8,6 +8,7 @@ "update:local": "./scripts/update.sh localhost mainnet", "codegen": "graph codegen", "build": "graph build", + "test": "graph test", "clean": "graph clean && rm subgraph.yaml.bak.*", "deploy:arbitrum-goerli": "graph deploy --product hosted-service kleros/kleros-v2-core-testnet-2", "deploy:arbitrum-goerli-devnet": "graph deploy --product hosted-service kleros/kleros-v2-core-devnet", @@ -30,7 +31,8 @@ "@graphprotocol/graph-cli": "0.52.0", "@kleros/kleros-v2-eslint-config": "workspace:^", "@kleros/kleros-v2-prettier-config": "workspace:^", - "gluegun": "^5.1.2" + "gluegun": "^5.1.2", + "matchstick-as": "0.6.0-beta.2" }, "dependenciesComments": { "@graphprotocol/graph-cli": "pinned because of this issue: https://github.com/graphprotocol/graph-tooling/issues/1399#issuecomment-1676104540" diff --git a/subgraph/schema.graphql b/subgraph/schema.graphql index ad646bee1..e0826be6e 100644 --- a/subgraph/schema.graphql +++ b/subgraph/schema.graphql @@ -19,6 +19,7 @@ interface DisputeKitDispute { coreDispute: Dispute! localRounds: [DisputeKitRound!]! @derivedFrom(field: "localDispute") currentLocalRoundIndex: BigInt! + timestamp: BigInt! } interface DisputeKitRound { @@ -72,6 +73,7 @@ type User @entity { totalResolvedDisputes: BigInt! totalDisputes: BigInt! totalCoherent: BigInt! + coherenceScore: BigInt! totalAppealingDisputes: BigInt! votes: [Vote!]! @derivedFrom(field: "juror") contributions: [Contribution!]! @derivedFrom(field: "contributor") @@ -236,6 +238,7 @@ type ClassicDispute implements DisputeKitDispute @entity { coreDispute: Dispute! localRounds: [DisputeKitRound!]! @derivedFrom(field: "localDispute") currentLocalRoundIndex: BigInt! + timestamp: BigInt! numberOfChoices: BigInt! extraData: Bytes! diff --git a/subgraph/src/entities/ClassicDispute.ts b/subgraph/src/entities/ClassicDispute.ts index 9d49c7210..bc0604517 100644 --- a/subgraph/src/entities/ClassicDispute.ts +++ b/subgraph/src/entities/ClassicDispute.ts @@ -9,5 +9,6 @@ export function createClassicDisputeFromEvent(event: DisputeCreation): void { classicDispute.currentLocalRoundIndex = ZERO; classicDispute.numberOfChoices = event.params._numberOfChoices; classicDispute.extraData = event.params._extraData; + classicDispute.timestamp = event.block.timestamp; classicDispute.save(); } diff --git a/subgraph/src/entities/JurorTokensPerCourt.ts b/subgraph/src/entities/JurorTokensPerCourt.ts index cccf06d7b..c42cfd4b3 100644 --- a/subgraph/src/entities/JurorTokensPerCourt.ts +++ b/subgraph/src/entities/JurorTokensPerCourt.ts @@ -38,7 +38,7 @@ export function updateJurorStake(jurorAddress: string, courtID: string, contract const jurorBalance = contract.getJurorBalance(Address.fromString(jurorAddress), BigInt.fromString(courtID)); const previousStake = jurorTokens.staked; const previousTotalStake = juror.totalStake; - jurorTokens.staked = jurorBalance.value0; + jurorTokens.staked = jurorBalance.value2; jurorTokens.locked = jurorBalance.value1; jurorTokens.save(); const stakeDelta = getDelta(previousStake, jurorTokens.staked); @@ -47,7 +47,7 @@ export function updateJurorStake(jurorAddress: string, courtID: string, contract court.stake = court.stake.plus(stakeDelta); updateStakedPNK(stakeDelta, timestamp); const activeJurorsDelta = getActivityDelta(previousTotalStake, newTotalStake); - const stakedJurorsDelta = getActivityDelta(previousStake, jurorBalance.value0); + const stakedJurorsDelta = getActivityDelta(previousStake, jurorBalance.value2); court.numberStakedJurors = court.numberStakedJurors.plus(stakedJurorsDelta); updateActiveJurors(activeJurorsDelta, timestamp); juror.save(); diff --git a/subgraph/src/entities/User.ts b/subgraph/src/entities/User.ts index 918878bcb..85a9da833 100644 --- a/subgraph/src/entities/User.ts +++ b/subgraph/src/entities/User.ts @@ -1,7 +1,20 @@ -import { BigInt } from "@graphprotocol/graph-ts"; +import { BigInt, BigDecimal } from "@graphprotocol/graph-ts"; import { User } from "../../generated/schema"; import { ONE, ZERO } from "../utils"; +export function computeCoherenceScore(totalCoherent: BigInt, totalResolvedDisputes: BigInt): BigInt { + const smoothingFactor = BigDecimal.fromString("10"); + + let denominator = totalResolvedDisputes.toBigDecimal().plus(smoothingFactor); + let coherencyRatio = totalCoherent.toBigDecimal().div(denominator); + + const coherencyScore = coherencyRatio.times(BigDecimal.fromString("100")); + + const roundedScore = coherencyScore.plus(BigDecimal.fromString("0.5")); + + return BigInt.fromString(roundedScore.toString().split(".")[0]); +} + export function ensureUser(id: string): User { const user = User.load(id); @@ -24,6 +37,7 @@ export function createUserFromAddress(id: string): User { user.totalAppealingDisputes = ZERO; user.totalDisputes = ZERO; user.totalCoherent = ZERO; + user.coherenceScore = ZERO; user.save(); return user; @@ -52,6 +66,7 @@ export function resolveUserDispute(id: string, previousFeeAmount: BigInt, feeAmo user.totalCoherent = user.totalCoherent.plus(ONE); } } + user.coherenceScore = computeCoherenceScore(user.totalCoherent, user.totalResolvedDisputes); user.save(); return; } @@ -61,5 +76,6 @@ export function resolveUserDispute(id: string, previousFeeAmount: BigInt, feeAmo user.totalCoherent = user.totalCoherent.plus(ONE); } user.activeDisputes = user.activeDisputes.minus(ONE); + user.coherenceScore = computeCoherenceScore(user.totalCoherent, user.totalResolvedDisputes); user.save(); } diff --git a/subgraph/tests/user.test.ts b/subgraph/tests/user.test.ts new file mode 100644 index 000000000..c69548b48 --- /dev/null +++ b/subgraph/tests/user.test.ts @@ -0,0 +1,9 @@ +import { assert, test, describe } from "matchstick-as/assembly/index"; +import { BigInt } from "@graphprotocol/graph-ts"; +import { computeCoherenceScore } from "../src/entities/User"; + +describe("Compute coherence score", () => { + test("Slam BigInts together", () => { + assert.bigIntEquals(BigInt.fromI32(8), computeCoherenceScore(BigInt.fromI32(1), BigInt.fromI32(2))); + }); +}); diff --git a/web/src/assets/pngs/dashboard/aristoteles.png b/web/src/assets/pngs/dashboard/aristoteles.png index 98710b0f2..9bc2f76fc 100644 Binary files a/web/src/assets/pngs/dashboard/aristoteles.png and b/web/src/assets/pngs/dashboard/aristoteles.png differ diff --git a/web/src/assets/pngs/dashboard/diogenes.png b/web/src/assets/pngs/dashboard/diogenes.png index 1c326ca1d..74afcc18b 100644 Binary files a/web/src/assets/pngs/dashboard/diogenes.png and b/web/src/assets/pngs/dashboard/diogenes.png differ diff --git a/web/src/assets/pngs/dashboard/plato.png b/web/src/assets/pngs/dashboard/plato.png index 7fb43789e..5b0ec9a87 100644 Binary files a/web/src/assets/pngs/dashboard/plato.png and b/web/src/assets/pngs/dashboard/plato.png differ diff --git a/web/src/assets/pngs/dashboard/pythagoras.png b/web/src/assets/pngs/dashboard/pythagoras.png index 7a4e49957..327b1ea1b 100644 Binary files a/web/src/assets/pngs/dashboard/pythagoras.png and b/web/src/assets/pngs/dashboard/pythagoras.png differ diff --git a/web/src/assets/pngs/dashboard/socrates.png b/web/src/assets/pngs/dashboard/socrates.png index 6350b40f0..81d85195c 100644 Binary files a/web/src/assets/pngs/dashboard/socrates.png and b/web/src/assets/pngs/dashboard/socrates.png differ diff --git a/web/src/assets/svgs/menu-icons/help.svg b/web/src/assets/svgs/menu-icons/help.svg index 71c5d151e..1c89e1fcf 100644 --- a/web/src/assets/svgs/menu-icons/help.svg +++ b/web/src/assets/svgs/menu-icons/help.svg @@ -1,3 +1,3 @@ - - + + diff --git a/web/src/assets/svgs/mini-guides/appeal/crowdfund-appeal.svg b/web/src/assets/svgs/mini-guides/appeal/crowdfund-appeal.svg new file mode 100644 index 000000000..ff5a5f1ca --- /dev/null +++ b/web/src/assets/svgs/mini-guides/appeal/crowdfund-appeal.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/web/src/assets/svgs/mini-guides/appeal/payoff-simulator.svg b/web/src/assets/svgs/mini-guides/appeal/payoff-simulator.svg new file mode 100644 index 000000000..c01b0acf7 --- /dev/null +++ b/web/src/assets/svgs/mini-guides/appeal/payoff-simulator.svg @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/web/src/assets/svgs/mini-guides/appeal/stage-one.svg b/web/src/assets/svgs/mini-guides/appeal/stage-one.svg new file mode 100644 index 000000000..fe3933e8d --- /dev/null +++ b/web/src/assets/svgs/mini-guides/appeal/stage-one.svg @@ -0,0 +1,59 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/web/src/assets/svgs/mini-guides/appeal/stage-two.svg b/web/src/assets/svgs/mini-guides/appeal/stage-two.svg new file mode 100644 index 000000000..acea9b8a3 --- /dev/null +++ b/web/src/assets/svgs/mini-guides/appeal/stage-two.svg @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/web/src/assets/svgs/mini-guides/binary-voting/voting-module.svg b/web/src/assets/svgs/mini-guides/binary-voting/voting-module.svg new file mode 100644 index 000000000..791289b53 --- /dev/null +++ b/web/src/assets/svgs/mini-guides/binary-voting/voting-module.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/web/src/assets/svgs/mini-guides/onboarding/how-it-works.svg b/web/src/assets/svgs/mini-guides/onboarding/how-it-works.svg new file mode 100644 index 000000000..c4fdd0567 --- /dev/null +++ b/web/src/assets/svgs/mini-guides/onboarding/how-it-works.svg @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/web/src/assets/svgs/mini-guides/onboarding/what-do-i-need.svg b/web/src/assets/svgs/mini-guides/onboarding/what-do-i-need.svg new file mode 100644 index 000000000..97f325c4e --- /dev/null +++ b/web/src/assets/svgs/mini-guides/onboarding/what-do-i-need.svg @@ -0,0 +1,81 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/web/src/assets/svgs/mini-guides/ranked-voting/voting-module.svg b/web/src/assets/svgs/mini-guides/ranked-voting/voting-module.svg new file mode 100644 index 000000000..feea37c68 --- /dev/null +++ b/web/src/assets/svgs/mini-guides/ranked-voting/voting-module.svg @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/web/src/assets/svgs/mini-guides/staking/court-header.svg b/web/src/assets/svgs/mini-guides/staking/court-header.svg new file mode 100644 index 000000000..f8ef2664c --- /dev/null +++ b/web/src/assets/svgs/mini-guides/staking/court-header.svg @@ -0,0 +1,107 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/web/src/assets/svgs/mini-guides/staking/juror-rewards.svg b/web/src/assets/svgs/mini-guides/staking/juror-rewards.svg new file mode 100644 index 000000000..49a3777da --- /dev/null +++ b/web/src/assets/svgs/mini-guides/staking/juror-rewards.svg @@ -0,0 +1,72 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/web/src/assets/svgs/mini-guides/staking/notifications.svg b/web/src/assets/svgs/mini-guides/staking/notifications.svg new file mode 100644 index 000000000..8fd69c1fd --- /dev/null +++ b/web/src/assets/svgs/mini-guides/staking/notifications.svg @@ -0,0 +1,37 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/web/src/assets/svgs/mini-guides/staking/staking-section.svg b/web/src/assets/svgs/mini-guides/staking/staking-section.svg new file mode 100644 index 000000000..faafd141e --- /dev/null +++ b/web/src/assets/svgs/mini-guides/staking/staking-section.svg @@ -0,0 +1,39 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/web/src/assets/svgs/styled/pnk.svg b/web/src/assets/svgs/styled/pnk.svg index 56a546d4c..f48cfb58f 100644 --- a/web/src/assets/svgs/styled/pnk.svg +++ b/web/src/assets/svgs/styled/pnk.svg @@ -1,9 +1,9 @@ - - + + - - - + + + diff --git a/web/src/components/CasesDisplay/Search.tsx b/web/src/components/CasesDisplay/Search.tsx index 6bb6e4b5f..42c911d58 100644 --- a/web/src/components/CasesDisplay/Search.tsx +++ b/web/src/components/CasesDisplay/Search.tsx @@ -1,7 +1,8 @@ import React, { useMemo, useState } from "react"; +import styled, { css } from "styled-components"; +import { landscapeStyle } from "styles/landscapeStyle"; 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 { rootCourtToItems, useCourtTree } from "queries/useCourtTree"; @@ -9,6 +10,21 @@ import { isUndefined } from "utils/index"; import { decodeURIFilter, encodeURIFilter, useRootPath } from "utils/uri"; const Container = styled.div` + display: flex; + flex-direction: column; + gap: 4px; + + ${landscapeStyle( + () => + css` + flex-direction: row; + gap: calc(4px + (22 - 4) * (min(max(100vw, 375px), 1250px) - 375px) / 875); + ` + )} +`; + +const SearchBarContainer = styled.div` + width: 100%; display: flex; flex-wrap: wrap; gap: 8px; @@ -54,15 +70,7 @@ const Search: React.FC = () => { }, [courtTreeData]); return ( -
- - setSearch(e.target.value)} - /> - + {items ? ( { ) : ( )} -
+ + setSearch(e.target.value)} + /> + + ); }; diff --git a/web/src/components/CasesDisplay/Stats.tsx b/web/src/components/CasesDisplay/Stats.tsx index a3b04193e..9f453692d 100644 --- a/web/src/components/CasesDisplay/Stats.tsx +++ b/web/src/components/CasesDisplay/Stats.tsx @@ -7,13 +7,17 @@ const FieldWrapper = styled.div` `; const SeparatorLabel = styled.label` - margin-left: 8px; - margin-right: 8px; + margin: 0 8px; + color: ${({ theme }) => theme.primaryText}; +`; + +const StyledLabel = styled.label` + color: ${({ theme }) => theme.primaryText}; `; const Field: React.FC<{ label: string; value: string }> = ({ label, value }) => ( - + {label} {value} ); diff --git a/web/src/components/CasesDisplay/StatsAndFilters.tsx b/web/src/components/CasesDisplay/StatsAndFilters.tsx index 3d878aeb7..42af83f81 100644 --- a/web/src/components/CasesDisplay/StatsAndFilters.tsx +++ b/web/src/components/CasesDisplay/StatsAndFilters.tsx @@ -7,7 +7,8 @@ const Container = styled.div` display: flex; flex-wrap: wrap; gap: 8px; - margin-top: 8px; + margin-top: 11px; + justify-content: space-between; `; const StatsAndFilters: React.FC = ({ totalDisputes, closedDisputes }) => ( diff --git a/web/src/components/CasesDisplay/index.tsx b/web/src/components/CasesDisplay/index.tsx index 5a50795ca..03dd742e4 100644 --- a/web/src/components/CasesDisplay/index.tsx +++ b/web/src/components/CasesDisplay/index.tsx @@ -4,9 +4,16 @@ import Search from "./Search"; import StatsAndFilters from "./StatsAndFilters"; import CasesGrid, { ICasesGrid } from "./CasesGrid"; -const StyledHR = styled.hr` - margin-top: 24px; - margin-bottom: 24px; +const Divider = styled.hr` + display: flex; + border: none; + height: 1px; + background-color: ${({ theme }) => theme.stroke}; + margin: calc(20px + (24 - 20) * (min(max(100vw, 375px), 1250px) - 375px) / 875) 0; +`; + +const StyledTitle = styled.h1` + margin-bottom: calc(32px + (48 - 32) * (min(max(100vw, 375px), 1250px) - 375px) / 875) !important; `; interface ICasesDisplay extends ICasesGrid { @@ -29,10 +36,10 @@ const CasesDisplay: React.FC = ({ }) => { return (
-

{title}

+ {title} - + {disputes?.length === 0 ? (

No cases found

diff --git a/web/src/components/ConnectWallet/AccountDisplay.tsx b/web/src/components/ConnectWallet/AccountDisplay.tsx index d39e7c60e..1d677637e 100644 --- a/web/src/components/ConnectWallet/AccountDisplay.tsx +++ b/web/src/components/ConnectWallet/AccountDisplay.tsx @@ -96,8 +96,15 @@ const StyledAvatar = styled.img<{ size: `${number}` }>` height: ${({ size }) => size + "px"}; `; -export const IdenticonOrAvatar: React.FC<{ size: `${number}` }> = ({ size } = { size: "16" }) => { - const { address } = useAccount(); +interface IIdenticonOrAvatar { + size?: `${number}`; + address?: `0x${string}`; +} + +export const IdenticonOrAvatar: React.FC = ({ size = "16", address: propAddress }) => { + const { address: defaultAddress } = useAccount(); + const address = propAddress || defaultAddress; + const { data: name } = useEnsName({ address, chainId: 1, @@ -106,6 +113,7 @@ export const IdenticonOrAvatar: React.FC<{ size: `${number}` }> = ({ size } = { name, chainId: 1, }); + return avatar ? ( ) : ( @@ -113,12 +121,19 @@ export const IdenticonOrAvatar: React.FC<{ size: `${number}` }> = ({ size } = { ); }; -export const AddressOrName: React.FC = () => { - const { address } = useAccount(); +interface IAddressOrName { + address?: `0x${string}`; +} + +export const AddressOrName: React.FC = ({ address: propAddress }) => { + const { address: defaultAddress } = useAccount(); + const address = propAddress || defaultAddress; + const { data } = useEnsName({ address, chainId: 1, }); + return ; }; diff --git a/web/src/components/Field.tsx b/web/src/components/Field.tsx index 352c45bad..226b8946c 100644 --- a/web/src/components/Field.tsx +++ b/web/src/components/Field.tsx @@ -18,7 +18,7 @@ const FieldContainer = styled.div` svg { fill: ${({ theme }) => theme.secondaryPurple}; margin-right: 8px; - width: 15px; + width: 14px; } .link { diff --git a/web/src/components/HowItWorks.tsx b/web/src/components/HowItWorks.tsx new file mode 100644 index 000000000..6791b400c --- /dev/null +++ b/web/src/components/HowItWorks.tsx @@ -0,0 +1,49 @@ +import React from "react"; +import styled from "styled-components"; +import BookOpenIcon from "tsx:assets/svgs/icons/book-open.svg"; + +const Container = styled.div` + display: flex; + align-items: center; + gap: 8px; + color: ${({ theme }) => theme.primaryBlue}; + + &, + & * { + cursor: pointer; + } + + &:hover { + text-decoration: underline; + } + + svg { + path { + fill: ${({ theme }) => theme.primaryBlue}; + } + } +`; + +const StyledLabel = styled.label` + color: ${({ theme }) => theme.primaryBlue}; +`; + +interface IHowItWorks { + isMiniGuideOpen: boolean; + toggleMiniGuide: () => void; + MiniGuideComponent: React.ComponentType<{ toggleMiniGuide: () => void }>; +} + +const HowItWorks: React.FC = ({ isMiniGuideOpen, toggleMiniGuide, MiniGuideComponent }) => { + return ( + <> + + + How it works + + {isMiniGuideOpen && } + + ); +}; + +export default HowItWorks; diff --git a/web/src/components/Popup/Description/StakeWithdraw.tsx b/web/src/components/Popup/Description/StakeWithdraw.tsx index af639ad84..e781a462b 100644 --- a/web/src/components/Popup/Description/StakeWithdraw.tsx +++ b/web/src/components/Popup/Description/StakeWithdraw.tsx @@ -1,9 +1,9 @@ import React from "react"; import styled from "styled-components"; -import { isUndefined } from "utils/index"; +import { formatUnits } from "viem"; import { useAccount } from "wagmi"; +import { isUndefined } from "utils/index"; import { useKlerosCoreGetJurorBalance } from "hooks/contracts/generated"; -import { format } from "src/pages/Dashboard/Courts/CourtCard"; import KlerosLogo from "tsx:svgs/icons/kleros.svg"; const Container = styled.div` @@ -88,7 +88,7 @@ const StakeWithdraw: React.FC = ({ pnkStaked, courtName, isStake My Stake:{" "} - {`${format(jurorBalance?.[0])} PNK`} + {`${formatUnits(jurorBalance?.[2] ?? BigInt(0), 18)} PNK`} ); diff --git a/web/src/components/Popup/MiniGuides/Appeal/CrowdfundAppeal.tsx b/web/src/components/Popup/MiniGuides/Appeal/CrowdfundAppeal.tsx new file mode 100644 index 000000000..cf2fcadd9 --- /dev/null +++ b/web/src/components/Popup/MiniGuides/Appeal/CrowdfundAppeal.tsx @@ -0,0 +1,38 @@ +import React from "react"; +import styled from "styled-components"; +import CrowdfundAppealSvg from "tsx:assets/svgs/mini-guides/appeal/crowdfund-appeal.svg"; +import { StyledImage } from "../PageContentsTemplate"; + +const StyledCrowdfundAppealSvg = styled(CrowdfundAppealSvg)` + [class$="rect-bg"] { + fill: ${({ theme }) => theme.whiteBackground}; + stroke: ${({ theme }) => theme.stroke}; + } + + [class$="path-1"], + [class$="path-2"], + [class$="path-3"] { + fill: ${({ theme }) => theme.primaryText}; + } + + [class$="rect-fg"] { + fill: ${({ theme }) => theme.whiteBackground}; + stroke: ${({ theme }) => theme.stroke}; + } + + [class$="rect-accent"] { + fill: ${({ theme }) => theme.primaryBlue}; + } + + [class$="path-4"] { + fill: ${({ theme }) => theme.whiteBackground}; + } + + [class$="path-5"] { + fill: ${({ theme }) => theme.secondaryText}; + } +`; + +const CrowdfundAppeal: React.FC = () => ; + +export default CrowdfundAppeal; diff --git a/web/src/components/Popup/MiniGuides/Appeal/PayoffSimulator.tsx b/web/src/components/Popup/MiniGuides/Appeal/PayoffSimulator.tsx new file mode 100644 index 000000000..7e0a16680 --- /dev/null +++ b/web/src/components/Popup/MiniGuides/Appeal/PayoffSimulator.tsx @@ -0,0 +1,77 @@ +import React from "react"; +import styled from "styled-components"; +import PayoffSimulatorSvg from "tsx:assets/svgs/mini-guides/appeal/payoff-simulator.svg"; +import { StyledImage } from "../PageContentsTemplate"; + +const StyledPayoffSimulatorSvg = styled(PayoffSimulatorSvg)` + [class$="circle-1"] { + fill: ${({ theme }) => theme.successLight}; + } + + [class$="rect-2"] { + fill: ${({ theme }) => theme.mediumBlue}; + } + + [class$="circle-2"] { + fill: ${({ theme }) => theme.mediumPurple}; + } + + [class$="rect-1"], + [class$="rect-5"] { + fill: ${({ theme }) => theme.whiteBackground}; + stroke: ${({ theme }) => theme.stroke}; + } + + [class$="rect-6"], + [class$="rect-7"], + [class$="rect-8"] { + fill: ${({ theme }) => theme.white}; + } + + [class$="path-2"] { + fill: ${({ theme }) => theme.success}; + } + + [class$="path-3"], + [class$="path-16"] { + fill: ${({ theme }) => theme.secondaryPurple}; + } + + [class$="path-11"], + [class$="path-13"] { + fill: ${({ theme }) => theme.whiteBackground}; + } + + [class$="path-4"], + [class$="path-5"], + [class$="path-6"], + [class$="path-7"], + [class$="path-8"], + [class$="path-9"], + [class$="path-10"], + [class$="path-12"], + [class$="path-14"], + [class$="path-15"], + [class$="path-17"] { + fill: ${({ theme }) => theme.primaryText}; + } + + [class$="rect-3"], + [class$="rect-4"] { + fill: ${({ theme }) => theme.primaryBlue}; + } + + [class$="line-1"], + [class$="line-2"], + [class$="path-1"] { + stroke: ${({ theme }) => theme.mediumBlue}; + } + + [class$="path-1"] { + fill: ${({ theme }) => theme.lightBlue}; + } +`; + +const PayoffSimulator: React.FC = () => ; + +export default PayoffSimulator; diff --git a/web/src/components/Popup/MiniGuides/Appeal/StageOne.tsx b/web/src/components/Popup/MiniGuides/Appeal/StageOne.tsx new file mode 100644 index 000000000..fe6d47047 --- /dev/null +++ b/web/src/components/Popup/MiniGuides/Appeal/StageOne.tsx @@ -0,0 +1,74 @@ +import React from "react"; +import styled from "styled-components"; +import StageOneSvg from "tsx:assets/svgs/mini-guides/appeal/stage-one.svg"; +import { StyledImage } from "../PageContentsTemplate"; + +const StyledStageOneSvg = styled(StageOneSvg)` + [class$="rect-1"], + [class$="rect-2"], + [class$="rect-6"], + [class$="circle-1"], + [class$="circle-2"] { + fill: ${({ theme }) => theme.whiteBackground}; + } + + [class$="rect-4"], + [class$="rect-8"] { + fill: ${({ theme }) => theme.stroke}; + } + + [class$="rect-5"], + [class$="path-9"], + [class$="path-10"] { + fill: ${({ theme }) => theme.secondaryPurple}; + } + + [class$="rect-9"], + [class$="circle-3"] { + fill: ${({ theme }) => theme.primaryBlue}; + } + + [class$="rect-10"] { + fill: ${({ theme }) => theme.lightBlue}; + stroke: ${({ theme }) => theme.mediumBlue}; + } + + [class$="rect-11"], + [class$="rect-12"] { + fill: ${({ theme }) => theme.white}; + } + + [class$="path-2"], + [class$="path-6"] { + fill: ${({ theme }) => theme.primaryText}; + } + + [class$="path-1"], + [class$="path-5"] { + fill: ${({ theme }) => theme.secondaryText}; + } + + [class$="path-3"], + [class$="path-4"] { + fill: ${({ theme }) => theme.success}; + } + + [class$="path-7"], + [class$="path-8"] { + fill: ${({ theme }) => theme.warning}; + } + + [class$="rect-3"], + [class$="rect-7"], + [class$="circle-1"] { + stroke: ${({ theme }) => theme.stroke}; + } + + [class$="circle-2"] { + stroke: ${({ theme }) => theme.primaryBlue}; + } +`; + +const StageOne: React.FC = () => ; + +export default StageOne; diff --git a/web/src/components/Popup/MiniGuides/Appeal/StageTwo.tsx b/web/src/components/Popup/MiniGuides/Appeal/StageTwo.tsx new file mode 100644 index 000000000..f625d0034 --- /dev/null +++ b/web/src/components/Popup/MiniGuides/Appeal/StageTwo.tsx @@ -0,0 +1,55 @@ +import React from "react"; +import styled from "styled-components"; +import StageTwoSvg from "tsx:assets/svgs/mini-guides/appeal/stage-two.svg"; +import { StyledImage } from "../PageContentsTemplate"; + +const StyledStageTwoSvg = styled(StageTwoSvg)` + [class$="rect-1"] { + fill: ${({ theme }) => theme.whiteBackground}; + } + + [class$="rect-2"], + [class$="rect-6"] { + fill: ${({ theme }) => theme.lightBlue}; + stroke: ${({ theme }) => theme.mediumBlue}; + } + + [class$="rect-3"], + [class$="path-2"], + [class$="path-3"] { + fill: ${({ theme }) => theme.success}; + } + + [class$="rect-4"] { + fill: ${({ theme }) => theme.stroke}; + } + + [class$="rect-5"] { + fill: ${({ theme }) => theme.primaryBlue}; + } + + [class$="rect-7"] { + fill: ${({ theme }) => theme.white}; + } + + [class$="rect-6"], + [class$="line-1"] { + stroke: ${({ theme }) => theme.mediumBlue}; + } + + [class$="path-1"], + [class$="path-4"], + [class$="path-5"], + [class$="path-6"] { + fill: ${({ theme }) => theme.primaryText}; + } + + [class$="path-7"], + [class$="path-8"] { + fill: ${({ theme }) => theme.secondaryPurple}; + } +`; + +const StageTwo: React.FC = () => ; + +export default StageTwo; diff --git a/web/src/components/Popup/MiniGuides/Appeal/index.tsx b/web/src/components/Popup/MiniGuides/Appeal/index.tsx new file mode 100644 index 000000000..fbd1da38f --- /dev/null +++ b/web/src/components/Popup/MiniGuides/Appeal/index.tsx @@ -0,0 +1,59 @@ +import React from "react"; +import CrowdfundAppeal from "./CrowdfundAppeal"; +import PayoffSimulator from "./PayoffSimulator"; +import StageOne from "./StageOne"; +import StageTwo from "./StageTwo"; +import PageContentsTemplate from "../PageContentsTemplate"; + +const leftPageContents = [ + { + title: "Appeal", + paragraphs: [ + "If after the jury has reached a decision, a party is not satisfied (because she thinks the result was" + + " unfair), she can appeal and have the dispute ruled again. Each new appeal instance will have twice the" + + " previous number of jurors plus one.", + ], + }, + { + title: "Appeal: Stage 1", + paragraphs: [ + "The jury decision is appealed when stages 1 and 2 are fully funded. In stage 1, one of the losing options" + + " must be fully funded. If no option is fully funded in time the jury decision is maintained.", + ], + }, + { + title: "Appeal: Stage 2", + paragraphs: [ + "Now, options compete together against the option fully funded at stage 1. The sum of funds must reach 100%." + + " If it's not fully funded in time the option fully funded at stage 1 is declared the winner of the case. ", + ], + }, + { + title: "Crowdfunding Rewards", + paragraphs: [ + "Anyone can contribute to the crowdfunding of the appeal fees. Crowdfunders can win rewards in case the option" + + " they fund wins. See how much you can earn by funding appeals, at the payoff simulator.", + ], + }, +]; + +const rightPageComponents = [CrowdfundAppeal, StageOne, StageTwo, PayoffSimulator]; + +interface IAppeal { + toggleMiniGuide: () => void; +} + +const Appeal: React.FC = ({ toggleMiniGuide }) => { + return ( + + ); +}; + +export default Appeal; diff --git a/web/src/components/Popup/MiniGuides/BinaryVoting/VotingModule.tsx b/web/src/components/Popup/MiniGuides/BinaryVoting/VotingModule.tsx new file mode 100644 index 000000000..fcb7a4938 --- /dev/null +++ b/web/src/components/Popup/MiniGuides/BinaryVoting/VotingModule.tsx @@ -0,0 +1,39 @@ +import React from "react"; +import styled from "styled-components"; +import VotingModuleSvg from "tsx:assets/svgs/mini-guides/binary-voting/voting-module.svg"; +import { StyledImage } from "../PageContentsTemplate"; + +const StyledVotingModuleSvg = styled(VotingModuleSvg)` + [class$="rect-1"], + [class$="rect-4"], + [class$="path-1"], + [class$="path-2"] { + fill: ${({ theme }) => theme.whiteBackground}; + } + + [class$="rect-2"], + [class$="rect-3"], + [class$="path-4"] { + fill: ${({ theme }) => theme.primaryBlue}; + } + + [class$="path-3"] { + fill: ${({ theme }) => theme.lightBlue}; + } + + [class$="path-5"] { + fill: ${({ theme }) => theme.secondaryText}; + } + + [class$="rect-1"] { + stroke: ${({ theme }) => theme.stroke}; + } + + [class$="rect-5"] { + stroke: ${({ theme }) => theme.primaryBlue}; + } +`; + +const VotingModule: React.FC = () => ; + +export default VotingModule; diff --git a/web/src/components/Popup/MiniGuides/BinaryVoting/index.tsx b/web/src/components/Popup/MiniGuides/BinaryVoting/index.tsx new file mode 100644 index 000000000..484cfc690 --- /dev/null +++ b/web/src/components/Popup/MiniGuides/BinaryVoting/index.tsx @@ -0,0 +1,45 @@ +import React from "react"; +import PageContentsTemplate from "../PageContentsTemplate"; +import JurorRewards from "../Staking/JurorRewards"; +import VotingModule from "./VotingModule"; + +const leftPageContents = [ + { + title: "Binary Voting", + paragraphs: [ + "Jurors choose one option to vote. The option which receives the majority of votes is considered the winner.", + "Refuse to Arbitrate is used when a dispute has been posted in the wrong court, has no clear outcome, or" + + " evidence is immoral and/or illegal. In case the majority decides to Refuse to Arbitrate, that option" + + " is considered the winner.", + ], + }, + { + title: "Juror Rewards", + paragraphs: [ + "Jurors whose vote is coherent with the final jury decision (after all the appeal instances) receive the" + + " Juror Rewards composed of arbitration fees (ETH) + PNK redistribution between jurors. Jurors whose vote" + + " is not coherent with the final jury decision, lose their locked PNK.", + ], + }, +]; + +const rightPageComponents = [VotingModule, JurorRewards]; + +interface IBinaryVoting { + toggleMiniGuide: () => void; +} + +const BinaryVoting: React.FC = ({ toggleMiniGuide }) => { + return ( + + ); +}; + +export default BinaryVoting; diff --git a/web/src/components/Popup/MiniGuides/JurorLevels.tsx b/web/src/components/Popup/MiniGuides/JurorLevels.tsx new file mode 100644 index 000000000..4ab5cf292 --- /dev/null +++ b/web/src/components/Popup/MiniGuides/JurorLevels.tsx @@ -0,0 +1,143 @@ +import React, { useState } from "react"; +import styled, { css } from "styled-components"; +import { landscapeStyle } from "styles/landscapeStyle"; +import { Card as _Card } from "@kleros/ui-components-library"; +import PixelArt from "pages/Dashboard/JurorInfo/PixelArt"; +import Coherency from "pages/Dashboard/JurorInfo/Coherency"; +import { Title, ParagraphsContainer, LeftContentContainer } from "./PageContentsTemplate"; +import Template from "./MainStructureTemplate"; + +const Card = styled(_Card)` + display: flex; + flex-direction: column; + align-items: center; + width: 234px; + height: 100%; + gap: 28px; + + padding: 24px; + + ${landscapeStyle( + () => css` + flex-direction: row; + width: 100%; + height: 236px; + ` + )} +`; + +const leftPageContents = [ + { + title: "Juror Level 1: Phytagoras", + paragraphs: [ + "Jurors are classified into distinct levels according to their performance starting from Level 1.", + "Level 1: Jurors with 0 cases arbitrated, OR Jurors with ≥ 1 case arbitrated with 0-70% of coherent votes.", + ], + }, + { + title: "Juror Level 2: Socrates", + paragraphs: ["Level 2: Jurors with ≥ 3 cases arbitrated with 70%-80% of coherent votes."], + }, + { + title: "Juror Level 3: Plato", + paragraphs: ["Level 3: Jurors with ≥ 7 cases arbitrated with 80%-90% of coherent votes."], + }, + { + title: "Juror Level 4: Aristotle", + paragraphs: ["Level 4: Jurors with ≥ 10 cases arbitrated with more than 90% of coherent votes."], + }, + { + title: "Juror Level 0: Diogenes", + paragraphs: [ + "There's a level for the low-performance/lazy jurors. Level 0: Jurors with ≥ 3 cases arbitrated" + + " with less than 50% of coherent votes.", + ], + }, +]; + +const userLevelData = [ + { + level: 1, + title: "Phytagoras", + totalCoherent: 6, + totalResolvedDisputes: 10, + }, + { + level: 2, + title: "Socrates", + totalCoherent: 7, + totalResolvedDisputes: 10, + }, + { + level: 3, + title: "Plato", + totalCoherent: 8, + totalResolvedDisputes: 10, + }, + { + level: 4, + title: "Aristotle", + totalCoherent: 9, + totalResolvedDisputes: 10, + }, + { + level: 0, + title: "Diogenes", + totalCoherent: 3, + totalResolvedDisputes: 10, + }, +]; + +const LeftContent: React.FC<{ currentPage: number }> = ({ currentPage }) => { + const { title, paragraphs } = leftPageContents[currentPage - 1]; + + return ( + + {title} + + {paragraphs.map((paragraph, index) => ( + + ))} + + + ); +}; + +const RightContent: React.FC<{ currentPage: number }> = ({ currentPage }) => { + const userData = userLevelData[currentPage - 1]; + return ( + + + + + ); +}; + +interface IJurorLevels { + toggleMiniGuide: () => void; +} + +const JurorLevels: React.FC = ({ toggleMiniGuide }) => { + const [currentPage, setCurrentPage] = useState(1); + + return ( +