Skip to content

Commit

Permalink
chore: implement challenging flow
Browse files Browse the repository at this point in the history
  • Loading branch information
epiqueras committed Sep 15, 2020
1 parent 65a5ef2 commit 201b193
Show file tree
Hide file tree
Showing 13 changed files with 335 additions and 32 deletions.
1 change: 1 addition & 0 deletions .eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -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.
],
],
Expand Down
Original file line number Diff line number Diff line change
@@ -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 (
<Card
variant="muted"
sx={{ width: 182 }}
mainSx={{ flexDirection: "column", padding: 0 }}
onClick={() => setType(type)}
active={type === currentType}
{...rest}
>
<Image sx={{ marginBottom: 2 }} src={imageSrc} />
<Text sx={{ fontWeight: "bold", marginBottom: 2 }}>{startCase}</Text>
<Text>{description}</Text>
</Card>
);
}
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 (
<Box sx={{ marginBottom: 2 }}>
<Input
sx={{ marginBottom: 1 }}
placeholder="Duplicate Address"
value={value}
onChange={(event) => setValue(event.target.value)}
/>
<Text>{message}</Text>
{isValidAddress && (
<NextLink href="/profile/[id]" as={`/profile/${value}`}>
<Link newTab>See Profile</Link>
</NextLink>
)}
</Box>
);
}
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 (
<Popup
contentStyle={{ width: undefined }}
trigger={
<Button
variant="secondary"
sx={{
marginY: 1,
padding: 1,
width: "100%",
}}
disabled={disputed && currentReasonIsNotDuplicate}
>
Challenge Request
</Button>
}
modal
>
{(close) => (
<Box sx={{ fontWeight: "bold", padding: 2 }}>
<Text sx={{ marginBottom: 1 }}>Deposit:</Text>
<Card
variant="muted"
sx={{ fontSize: 2, marginBottom: 3 }}
mainSx={{ padding: 0 }}
>
<Text>
{totalCost && `${web3.utils.fromWei(totalCost)} ETH Deposit`}
</Text>
</Card>
<Text sx={{ marginBottom: 1 }}>Challenge Type:</Text>
<Grid sx={{ marginBottom: 2 }} gap={1} columns={[1, 2, 4]}>
<ChallengeTypeCard
type={challengeReasonEnum.IncorrectSubmission}
setType={setType}
currentType={type}
disabled={usedReasons.IncorrectSubmission || disputed}
/>
<ChallengeTypeCard
type={challengeReasonEnum.Deceased}
setType={setType}
currentType={type}
disabled={usedReasons.Deceased || disputed}
/>
<ChallengeTypeCard
type={challengeReasonEnum.Duplicate}
setType={setType}
currentType={type}
disabled={usedReasons.Duplicate && currentReasonIsNotDuplicate}
/>
<ChallengeTypeCard
type={challengeReasonEnum.DoesNotExist}
setType={setType}
currentType={type}
disabled={usedReasons.DoesNotExist || disputed}
/>
</Grid>
{duplicateTypeSelected && (
<DuplicateInput
submissionID={submissionID}
setDuplicate={setDuplicate}
/>
)}
<Button
sx={{ display: "block", margin: "auto" }}
disabled={
!type || (duplicateTypeSelected && !duplicate) || !totalCost
}
onClick={() =>
send(
submissionID,
type.index,
duplicate || zeroAddress,
zeroAddress,
{ value: totalCost }
).then(() => close())
}
loading={loading}
>
Challenge Request
</Button>
</Box>
)}
</Popup>
);
}
Original file line number Diff line number Diff line change
@@ -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 (
<Text>
<Text sx={{ fontWeight: "bold" }}>{label}: </Text>
<TimeAgo datetime={datetime} />
{Date.now() < datetime && button}
</Text>
);
}
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 (
<>
<Deadline
Expand All @@ -51,6 +57,13 @@ export default function Deadlines({ submission, contract, status }) {
Number(challengePeriodDuration)) *
1000
}
button={
<ChallengeButton
request={request[0]}
contract={contract}
submissionID={id}
/>
}
/>
) : (
submissionTime && (
Expand Down
14 changes: 9 additions & 5 deletions _pages/profile/[id]/submission-details-card/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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]
);
Expand Down Expand Up @@ -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]
Expand Down Expand Up @@ -140,7 +140,7 @@ export default function SubmissionDetailsCard({ submission, contract }) {
</Text>
<Text count={2}>{evidence?.file?.bio}</Text>
<Box sx={{ marginY: 2, width: "100%" }}>
{status === submissionStatusEnum.Vouching ? (
{status === submissionStatusEnum.Vouching && (
<>
{totalCost?.gt(totalContribution) && (
<FundButton
Expand All @@ -151,7 +151,7 @@ export default function SubmissionDetailsCard({ submission, contract }) {
)}
<VouchButton submissionID={id} />
</>
) : null}
)}
</Box>
<Flex sx={{ width: "100%" }}>
<Box
Expand Down Expand Up @@ -182,7 +182,11 @@ export default function SubmissionDetailsCard({ submission, contract }) {
</Box>
</Flex>
<Box sx={{ marginTop: "auto" }}>
<Deadlines submission={submission} contract={contract} />
<Deadlines
submission={submission}
contract={contract}
status={status}
/>
</Box>
</Flex>
<Box sx={{ flex: 1, padding: 4 }}>
Expand Down
7 changes: 6 additions & 1 deletion components/card.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { Flex, Card as _Card } from "theme-ui";

export default function Card({
active,
header,
headerSx,
mainSx,
Expand All @@ -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}
>
<Flex sx={{ flexDirection: "column" }}>
{header && (
<Flex
Expand Down
10 changes: 8 additions & 2 deletions components/theme-provider.js
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ const theme = merge(merge(base, toTheme(typographyTheme)), {
boxShadow: "0 6px 90px rgba(255, 153, 0, 0.25)",
fontFamily: "heading",
fontSize: 0,
"&:hover": {
"&:hover:not([disabled])": {
boxShadow: "0 6px 20px rgba(255, 153, 0, 0.25)",
},
},
Expand All @@ -125,7 +125,13 @@ const theme = merge(merge(base, toTheme(typographyTheme)), {
borderWidth: 2,
fontSize: 1,
padding: 2,
"&:hover": {
"&:focus": {
outline: "none",
},
"&:disabled": {
backgroundColor: "skeleton",
},
"&:focus,&:hover:not([disabled]),&.active": {
borderColor: "primary",
},
},
Expand Down
Loading

0 comments on commit 201b193

Please sign in to comment.