diff --git a/.eslintrc.js b/.eslintrc.js index 65cbd437..c6843d0d 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -345,6 +345,7 @@ module.exports = { "(?=.*[A-F])#[0-9a-fA-F]{1,6}", // Don't use upper case letters in hex colors. "@js[x]", // Don't use a custom JSX pragma. "Style[d]", // Don't use "styled" components. + ",\\s+&", // Don't use spaces between comma separated selectors. "eslint-disabl[e]", // Don't disable rules. ], ], diff --git a/_pages/profile/[id]/submission-details-card/deadlines/challenge-button.js b/_pages/profile/[id]/submission-details-card/deadlines/challenge-button.js new file mode 100644 index 00000000..f91d8afb --- /dev/null +++ b/_pages/profile/[id]/submission-details-card/deadlines/challenge-button.js @@ -0,0 +1,224 @@ +import { + Box, + Button, + Card, + Grid, + Image, + Input, + Link, + NextLink, + Popup, + Text, + useContract, + useWeb3, +} from "@kleros/components"; +import { useEffect, useMemo, useState } from "react"; +import { graphql, useFragment } from "relay-hooks"; + +import { challengeReasonEnum, ethereumAddressRegExp, zeroAddress } from "data"; + +const challengeButtonFragments = { + contract: graphql` + fragment challengeButtonContract on Contract { + submissionChallengeBaseDeposit + sharedStakeMultiplier + } + `, + request: graphql` + fragment challengeButtonRequest on Request { + disputed + arbitrator + arbitratorExtraData + usedReasons + currentReason + } + `, +}; +function ChallengeTypeCard({ type, setType, currentType, ...rest }) { + const { imageSrc, startCase, description } = type; + return ( + setType(type)} + active={type === currentType} + {...rest} + > + + {startCase} + {description} + + ); +} +function DuplicateInput({ submissionID, setDuplicate }) { + const [value, setValue] = useState(""); + const isValidAddress = ethereumAddressRegExp.test(value); + const [submission] = useContract( + "proofOfHumanity", + "getSubmissionInfo", + useMemo( + () => ({ + args: [isValidAddress ? value : undefined], + }), + [isValidAddress, value] + ) + ); + + let message; + if (submissionID.toLowerCase() === value.toLowerCase()) + message = "A submission can not be a duplicate of itself."; + else if (isValidAddress && submission) + if (Number(submission.status) > 0 || submission.registered) + message = "Valid duplicate."; + else + message = + "A supposed duplicate should be either registered or pending registration."; + useEffect(() => { + if (message === "Valid duplicate.") setDuplicate(value); + else setDuplicate(); + }, [message, setDuplicate, value]); + return ( + + setValue(event.target.value)} + /> + {message} + {isValidAddress && ( + + See Profile + + )} + + ); +} +export default function ChallengeButton({ request, contract, submissionID }) { + const { + currentReason: _currentReason, + arbitrator, + arbitratorExtraData, + disputed, + usedReasons: _usedReasons, + } = useFragment(challengeButtonFragments.request, request); + const currentReason = challengeReasonEnum.parse(_currentReason); + const usedReasons = challengeReasonEnum.parse(_usedReasons); + const currentReasonIsNotDuplicate = + currentReason !== challengeReasonEnum.Duplicate; + + const [arbitrationCost] = useContract( + "klerosLiquid", + "arbitrationCost", + useMemo( + () => ({ + address: arbitrator, + args: [arbitratorExtraData], + }), + [arbitrator, arbitratorExtraData] + ) + ); + const { web3 } = useWeb3(); + const { sharedStakeMultiplier, submissionChallengeBaseDeposit } = useFragment( + challengeButtonFragments.contract, + contract + ); + const totalCost = arbitrationCost + ?.add( + arbitrationCost + .mul(web3.utils.toBN(sharedStakeMultiplier)) + .div(web3.utils.toBN(10000)) + ) + .add(web3.utils.toBN(submissionChallengeBaseDeposit)); + + const [type, setType] = useState(); + const duplicateTypeSelected = type === challengeReasonEnum.Duplicate; + const [duplicate, setDuplicate] = useState(); + const { send, loading } = useContract("proofOfHumanity", "challengeRequest"); + return ( + + Challenge Request + + } + modal + > + {(close) => ( + + Deposit: + + + {totalCost && `${web3.utils.fromWei(totalCost)} ETH Deposit`} + + + Challenge Type: + + + + + + + {duplicateTypeSelected && ( + + )} + + + )} + + ); +} diff --git a/_pages/profile/[id]/submission-details-card/deadlines.js b/_pages/profile/[id]/submission-details-card/deadlines/index.js similarity index 74% rename from _pages/profile/[id]/submission-details-card/deadlines.js rename to _pages/profile/[id]/submission-details-card/deadlines/index.js index 665e98f8..550a9d87 100644 --- a/_pages/profile/[id]/submission-details-card/deadlines.js +++ b/_pages/profile/[id]/submission-details-card/deadlines/index.js @@ -1,41 +1,47 @@ import { Text, TimeAgo } from "@kleros/components"; import { graphql, useFragment } from "relay-hooks"; +import ChallengeButton from "./challenge-button"; + import { submissionStatusEnum } from "data"; const deadlinesFragments = { contract: graphql` fragment deadlinesContract on Contract { challengePeriodDuration + ...challengeButtonContract } `, submission: graphql` fragment deadlinesSubmission on Submission { + id submissionTime renewalTimestamp request: requests(orderDirection: desc, first: 1) { lastStatusChange + ...challengeButtonRequest } } `, }; -function Deadline({ label, datetime }) { +function Deadline({ label, datetime, button }) { return ( {label}: + {Date.now() < datetime && button} ); } export default function Deadlines({ submission, contract, status }) { - const { request, submissionTime, renewalTimestamp } = useFragment( + const { request, id, submissionTime, renewalTimestamp } = useFragment( deadlinesFragments.submission, submission ); - const { challengePeriodDuration } = useFragment( + const { challengePeriodDuration } = (contract = useFragment( deadlinesFragments.contract, contract - ); + )); return ( <> + } /> ) : ( submissionTime && ( diff --git a/_pages/profile/[id]/submission-details-card/index.js b/_pages/profile/[id]/submission-details-card/index.js index 22175863..3c0d83b6 100644 --- a/_pages/profile/[id]/submission-details-card/index.js +++ b/_pages/profile/[id]/submission-details-card/index.js @@ -65,7 +65,7 @@ export default function SubmissionDetailsCard({ submission, contract }) { const contributions = useMemo( () => request.challenges[0].rounds[0].contributions.map((contribution) => - partyEnum.parse(contribution) + partyEnum.parse(contribution.values) ), [request] ); @@ -104,7 +104,7 @@ export default function SubmissionDetailsCard({ submission, contract }) { const totalContribution = useMemo( () => contributions.reduce( - (acc, { values: { Requester } }) => acc.add(web3.utils.toBN(Requester)), + (acc, { Requester }) => acc.add(web3.utils.toBN(Requester)), web3.utils.toBN(0) ), [contributions, web3.utils] @@ -140,7 +140,7 @@ export default function SubmissionDetailsCard({ submission, contract }) { {evidence?.file?.bio} - {status === submissionStatusEnum.Vouching ? ( + {status === submissionStatusEnum.Vouching && ( <> {totalCost?.gt(totalContribution) && ( - ) : null} + )} - + diff --git a/components/card.js b/components/card.js index 5576c37d..bfabd4a8 100644 --- a/components/card.js +++ b/components/card.js @@ -1,6 +1,7 @@ import { Flex, Card as _Card } from "theme-ui"; export default function Card({ + active, header, headerSx, mainSx, @@ -10,7 +11,11 @@ export default function Card({ ...rest }) { return ( - <_Card {...rest}> + <_Card + as={rest.disabled || rest.onClick ? "button" : _Card} + className={active ? "active" : undefined} + {...rest} + > {header && ( { const _enum = keys.reduce( (acc, key, index) => { @@ -23,7 +27,22 @@ const createEnum = (keys, parse) => { acc[value.startCase] = value; return acc; }, - { parse } + { + parse: + parse || + ((arrayOrKey) => + Array.isArray(arrayOrKey) + ? arrayOrKey.reduce((acc, key) => { + const value = _enum[key]; + acc[key] = value; + acc[value.index] = value; + acc[value.camelCase] = value; + acc[value.kebabCase] = value; + acc[value.startCase] = value; + return acc; + }, {}) + : _enum[arrayOrKey]), + } ); _enum.map = (callback) => keys.map((_, index) => callback(_enum[index])); _enum.find = (callback) => @@ -31,19 +50,6 @@ const createEnum = (keys, parse) => { return _enum; }; -export const partyEnum = createEnum(["Requester", "Challenger"], (struct) => - Object.keys(struct).reduce((acc, key) => { - acc[key] = - Array.isArray(struct[key]) && struct[key].length === 2 - ? { - [partyEnum.Requester.key]: struct[key][0], - [partyEnum.Challenger.key]: struct[key][1], - } - : struct[key]; - return acc; - }, {}) -); - export const submissionStatusEnum = createEnum( [ ["None", { kebabCase: undefined, startCase: "All" }], @@ -80,4 +86,41 @@ export const submissionStatusEnum = createEnum( : submissionStatusEnum[status] ); +export const partyEnum = createEnum(["Requester", "Challenger"], (array) => ({ + [partyEnum.Requester.key]: array[0], + [partyEnum.Challenger.key]: array[1], +})); + +export const challengeReasonEnum = createEnum([ + "None", + [ + "IncorrectSubmission", + { + imageSrc: "/images/incorrect-submission.png", + description: "Parts of the submission are incorrect.", + }, + ], + [ + "Deceased", + { + imageSrc: "/images/deceased.png", + description: "The submitter has passed away.", + }, + ], + [ + "Duplicate", + { + imageSrc: "/images/duplicate.png", + description: "The submitter is already registered.", + }, + ], + [ + "DoesNotExist", + { + imageSrc: "/images/does-not-exist.png", + description: "The submitter does not exist.", + }, + ], +]); + export const queryEnums = { status: submissionStatusEnum }; diff --git a/public/images/deceased.png b/public/images/deceased.png new file mode 100644 index 00000000..7eaba9ec Binary files /dev/null and b/public/images/deceased.png differ diff --git a/public/images/does-not-exist.png b/public/images/does-not-exist.png new file mode 100644 index 00000000..8d8c2d1c Binary files /dev/null and b/public/images/does-not-exist.png differ diff --git a/public/images/duplicate.png b/public/images/duplicate.png new file mode 100644 index 00000000..d870d154 Binary files /dev/null and b/public/images/duplicate.png differ diff --git a/public/images/incorrect-submission.png b/public/images/incorrect-submission.png new file mode 100644 index 00000000..b8f1ca48 Binary files /dev/null and b/public/images/incorrect-submission.png differ diff --git a/subgraph/src/mapping.ts b/subgraph/src/mapping.ts index 8e2dfe3b..f4751a89 100644 --- a/subgraph/src/mapping.ts +++ b/subgraph/src/mapping.ts @@ -43,11 +43,11 @@ import { Submission, } from "../generated/schema"; -function getStatus(reason: number): string { - if (reason == 0) return "None"; - if (reason == 1) return "Vouching"; - if (reason == 2) return "PendingRegistration"; - if (reason == 3) return "PendingRemoval"; +function getStatus(status: number): string { + if (status == 0) return "None"; + if (status == 1) return "Vouching"; + if (status == 2) return "PendingRegistration"; + if (status == 3) return "PendingRemoval"; return "Error"; }