diff --git a/public/logo/light-icon.svg b/public/logo/light-icon.svg new file mode 100644 index 0000000..b00e33f --- /dev/null +++ b/public/logo/light-icon.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/public/logo/new-tab.svg b/public/logo/new-tab.svg index 5bc0dec..3d8ca80 100644 --- a/public/logo/new-tab.svg +++ b/public/logo/new-tab.svg @@ -1,3 +1,3 @@ - + \ No newline at end of file diff --git a/public/logo/paperclip.svg b/public/logo/paperclip.svg index 733c872..c9c8b70 100644 --- a/public/logo/paperclip.svg +++ b/public/logo/paperclip.svg @@ -1,4 +1,4 @@ - + + c3.535,2.86,7.982,4.436,12.522,4.436l0,0c6.048,0,11.696-2.693,15.498-7.39C351.301,129.955,357.89,107.64,355.488,84.873z" fill="#009AFF"/> \ No newline at end of file diff --git a/schemas/request.gql b/schemas/request.gql index 3504c54..56a3656 100644 --- a/schemas/request.gql +++ b/schemas/request.gql @@ -68,7 +68,9 @@ query Request($id: ID!) { id } nbRounds - rounds(orderBy: creationTime) { + rounds(orderBy: index) { + creationTime + index requesterFund { amount } @@ -80,6 +82,9 @@ query Request($id: ID!) { arbitratorHistory { updateTime registrationMeta + id + arbitrator + extraData } } -} +} \ No newline at end of file diff --git a/src/app/Footer.tsx b/src/app/Footer.tsx index e883ecb..934ec58 100644 --- a/src/app/Footer.tsx +++ b/src/app/Footer.tsx @@ -7,7 +7,7 @@ const Footer: React.FC = () => ( className="flex items-center gap-2 text-sm" href="https://kleros.io/" > - SECURED BY{" "} + BUILT BY{" "} diff --git a/src/app/Header/Options.tsx b/src/app/Header/Options.tsx index 502d0cf..cbb4c62 100644 --- a/src/app/Header/Options.tsx +++ b/src/app/Header/Options.tsx @@ -1,10 +1,34 @@ -import ExternalLink from "components/ExternalLink" -import Popover from "components/Popover" -import React from "react"; import Image from "next/image"; - +import React, { useEffect, useState } from "react"; +import ExternalLink from "components/ExternalLink"; +import Popover from "components/Popover"; const Options: React.FC = () => { + const [isDarkMode, setIsDarkMode] = useState(false); + + useEffect(() => { + const savedTheme = localStorage.getItem("theme"); + if (savedTheme === "dark") { + document.documentElement.classList.add("dark"); + setIsDarkMode(true); + } else { + document.documentElement.classList.remove("dark"); + setIsDarkMode(false); + } + }, []); + + const toggleTheme = () => { + if (isDarkMode) { + document.documentElement.classList.remove("dark"); + localStorage.setItem("theme", "light"); + setIsDarkMode(false); + } else { + document.documentElement.classList.add("dark"); + localStorage.setItem("theme", "dark"); + setIsDarkMode(true); + } + }; + return (
@@ -49,7 +73,17 @@ const Options: React.FC = () => {
+ + toggle theme ); }; + export default Options; diff --git a/src/app/[pohid]/CrossChain.tsx b/src/app/[pohid]/CrossChain.tsx index 0fd8429..b9e3f1d 100644 --- a/src/app/[pohid]/CrossChain.tsx +++ b/src/app/[pohid]/CrossChain.tsx @@ -255,9 +255,9 @@ export default withClientConnected(function CrossChain({ return (
- Home chain + Home chain - + {homeChain.name}
@@ -271,7 +271,7 @@ export default withClientConnected(function CrossChain({ trigger={} >
- + Transfer your humanity to another chain. If you use a contract wallet make sure it has the same address on both chains. diff --git a/src/app/[pohid]/Revoke.tsx b/src/app/[pohid]/Revoke.tsx index a605690..9ec827b 100644 --- a/src/app/[pohid]/Revoke.tsx +++ b/src/app/[pohid]/Revoke.tsx @@ -103,18 +103,18 @@ export default withClientConnected(function Revoke({ >
- - Policy + + Policy - + In order to request removal you need to deposit - + {formatEth(cost)} {homeChain.nativeCurrency.symbol} - + Anyone can put a deposit claiming the removal to be incorrect. If no one does, the individual is removed from the list. If one does, a dispute is created. @@ -134,7 +134,7 @@ export default withClientConnected(function Revoke({
setFile(acceptedFiles[0])} > diff --git a/src/app/[pohid]/[chain]/[request]/ActionBar.tsx b/src/app/[pohid]/[chain]/[request]/ActionBar.tsx index d720218..af2bddf 100644 --- a/src/app/[pohid]/[chain]/[request]/ActionBar.tsx +++ b/src/app/[pohid]/[chain]/[request]/ActionBar.tsx @@ -24,6 +24,7 @@ import RemoveVouch from "./RemoveVouch"; import Vouch from "./Vouch"; import { getMyData } from "data/user"; import useSWR from "swr"; +import Appeal from "./Appeal"; enableReactUse(); @@ -52,6 +53,7 @@ interface ActionBarProps extends JSX.IntrinsicAttributes { onChainVouches: Address[]; offChainVouches: { voucher: Address; expiration: number; signature: Hash }[]; expired: boolean; + arbitrationHistory: { __typename?: "ArbitratorHistory" | undefined; updateTime: any; registrationMeta: string; id: string; arbitrator: any; extraData: any; } } export default withClientConnected(function ActionBar({ @@ -69,6 +71,7 @@ export default withClientConnected(function ActionBar({ offChainVouches, // advanceRequestsOnChainVouches, expired, + arbitrationHistory, }) { const chain = useChainParam()!; const { address } = useAccount(); @@ -277,7 +280,7 @@ export default withClientConnected(function ActionBar({ const statusColor = colorForStatus(status, revocation, expired); return ( -
+
Status (function ActionBar({ )} . + +
+ View case #{currentChallenge.disputeId} +
)} diff --git a/src/app/[pohid]/[chain]/[request]/Appeal.tsx b/src/app/[pohid]/[chain]/[request]/Appeal.tsx index a9872bf..defc835 100644 --- a/src/app/[pohid]/[chain]/[request]/Appeal.tsx +++ b/src/app/[pohid]/[chain]/[request]/Appeal.tsx @@ -3,44 +3,226 @@ import Accordion from "components/Accordion"; import Identicon from "components/Identicon"; import Progress from "components/Progress"; -import { formatEth } from "utils/misc"; +import { eth2Wei, formatEth } from "utils/misc"; import usePoHWrite from "contracts/hooks/usePoHWrite"; -import { Address, Hash } from "viem"; +import { Address } from "viem"; +import { useMemo, useRef, useState } from "react"; +import Field from "components/Field"; +import Modal from "components/Modal"; +import { SupportedChainId } from "config/chains"; +import { toast } from "react-toastify"; +import { useEffectOnce } from "@legendapp/state/react"; +import TimeAgo from "components/TimeAgo"; +import { useLoading } from "hooks/useLoading"; +import { APIPoH, StakeMultipliers } from "contracts/apis/APIPoH"; +import { APIArbitrator, ArbitratorsData, DisputeStatusEnum, SideEnum } from "contracts/apis/APIArbitrator"; +import { RequestQuery } from "generated/graphql"; -interface AppealProps { - pohId: Hash; + +interface SideFundingProps { + side: SideEnum; + arbitrator: Address; + disputeId: bigint; + contributor: Address; requester: Address; + requesterFunds: bigint; + appealCost: bigint; +} + +const SideFunding: React.FC = ({ + side, + disputeId, + arbitrator, + contributor, + requester, + requesterFunds, + appealCost, +}) => { + + const title = side === SideEnum.claimer? 'Claimer' : 'Challenger'; + const [requesterInput, setRequesterInput] = useState(0n); + const loading = useLoading(); + const errorRef = useRef(false); + + const value = formatEth(requesterFunds)*100/formatEth(appealCost); + const valueProgress = value > 100? 100 : value; + + const [prepareFundAppeal] = usePoHWrite( + "fundAppeal", + useMemo( + () => ({ + onLoading() { + loading.start(); + }, + onReady(fire) { + fire(); + }, + onFail() { + !errorRef.current && toast.info("Transaction is not possible! Do you have enough funds?"); + errorRef.current = true; + } + }), + [loading] + ) + ); + + return ( + +
+
+ +
+ {requester} + {title} +
+
+
+ setRequesterInput(eth2Wei(+v.target.value))} + /> + +
+ +
+
+ ) +} + +interface AppealProps { + pohId: Address; + requestIndex: number; + arbitrator: Address; + extraData: any, + contributor: Address; + claimer: Address; challenger: Address; disputeId: bigint; - requestIndex: bigint; - challengeIndex: bigint; - start: number; - end: number; - challengerFunds: bigint; - requesterFunds: bigint; + chainId: SupportedChainId; + currentChallenge: ArrayElement< + NonNullable["request"]>["challenges"] + >; } const Appeal: React.FC = ({ pohId, requestIndex, - challengeIndex, disputeId, - requester, + arbitrator, + extraData, + contributor, + chainId, + claimer, challenger, - start, - end, - challengerFunds, - requesterFunds, + currentChallenge }) => { - const [fundAppeal] = usePoHWrite("fundAppeal"); + const [totalClaimerCost, setTotalClaimerCost] = useState(0n); + const [totalChallengerCost, setTotalChallengerCost] = useState(0n); + const [formatedCurrentRuling, setFormatedCurrentRuling] = useState(""); + const defaultPeriod = [0n,0n]; + const [period, setPeriod] = useState(defaultPeriod); + const [disputeStatus, setDisputeStatus] = useState(DisputeStatusEnum.Appealable); + const [error, setError] = useState(false); + const errorRef = useRef(false); + const [loading, setLoading] = useState(true); + const [claimerFunds, setClaimerFunds] = useState(0n); + const [challengerFunds, setChallengerFunds] = useState(0n); + + useEffectOnce(() => { + const formatCurrentRuling = (currentRuling: SideEnum) => { + var text = "Undecided"; + switch (currentRuling) { + case SideEnum.claimer: + text = "Claimer wins"; + break; + case SideEnum.challenger: + text = "Challenger wins"; + break; + case SideEnum.shared: + text = "Shared"; + } + setFormatedCurrentRuling(text); + } - // const [requesterInput, setRequesterInput] = useState(0); - // const [challengerInput, setChallengerInput] = useState(0); + const calculateTotalCost = (appealCost: bigint, currentRuling: SideEnum, winnerMult: number, loserMult: number, sharedMult: number) => { + const getSideTotalCost = (sideMultiplier: number) => { + return Number(appealCost) + ((Number(appealCost) * sideMultiplier) / MULTIPLIER_DIVISOR); + } + const MULTIPLIER_DIVISOR = 10000; + + const claimerMultiplier = currentRuling === SideEnum.shared?sharedMult: currentRuling === SideEnum.claimer? winnerMult : loserMult; + const totalClaimerCost = getSideTotalCost(Number(claimerMultiplier)); + setTotalClaimerCost(BigInt(totalClaimerCost)); + + const challengerMultiplier = currentRuling === SideEnum.shared?sharedMult: currentRuling === SideEnum.claimer? loserMult : winnerMult; + const totalChallengerCost = getSideTotalCost(Number(challengerMultiplier)); + setTotalChallengerCost(BigInt(totalChallengerCost)); + } + const getAppealData = async () => { + try{ + const isPartiallyFunded = Number(currentChallenge.nbRounds)+1 === currentChallenge.rounds.length; + const claimerFunds = isPartiallyFunded? currentChallenge.rounds.at(-1)?.requesterFund.amount: 0n; + const challengerFunds = isPartiallyFunded? (currentChallenge.rounds.at(-1)?.challengerFund? currentChallenge.rounds.at(-1)?.challengerFund?.amount: 0n): 0n; + setClaimerFunds(claimerFunds); + setChallengerFunds(challengerFunds); + + const stakeMultipliers: StakeMultipliers = await APIPoH.getStakeMultipliers(chainId); + const winnerMult = stakeMultipliers.winnerStakeMultiplier; + const loserMult = stakeMultipliers.loserStakeMultiplier; + const sharedMult = stakeMultipliers.sharedStakeMultiplier; + + const arbitratorsData: ArbitratorsData = await APIArbitrator.getArbitratorsData(chainId, arbitrator, disputeId, extraData); + const status = arbitratorsData.status; + const cost = arbitratorsData.cost; + const period = arbitratorsData.period; + const currentRuling = arbitratorsData.currentRuling; + + setPeriod(period!); + setDisputeStatus(Number(status) as DisputeStatusEnum); + formatCurrentRuling(Number(currentRuling) as SideEnum); + calculateTotalCost(cost!, Number(currentRuling) as SideEnum, Number(winnerMult), Number(loserMult), Number(sharedMult)); + + setLoading(false); + } catch (e) { + !errorRef.current && toast.info("Unexpected error while reading appelate round info. Come back later"); + setError(true); + errorRef.current = true; + console.log(e); + } + } + getAppealData(); + }); + return ( - -
-

Appeal the decision

+ disputeStatus === DisputeStatusEnum.Appealable && !error && !loading? + + Fund Appeal (ends ) + } + > +
+

Appeal the decision: {formatedCurrentRuling}

In order to appeal the decision, you need to fully fund the crowdfunding deposit. The dispute will be sent to the jurors when the @@ -48,84 +230,31 @@ const Appeal: React.FC = ({ its side, the previous round winner should also fully fund its side, in order not to lose the case.

+
+ +
+ -
-
- -
- {requester} - Claimer -
-
-
- {/* setRequesterInput(+v.target.value)} - /> */} - -
- -
- -
-
- -
- {challenger} - Challenger -
-
-
- {/* setChallengerInput(+v.target.value)} - /> */} - -
- -
-
+ requester={challenger} + requesterFunds={challengerFunds} + appealCost={totalChallengerCost} + />
-
+ + : null ); }; diff --git a/src/app/[pohid]/[chain]/[request]/Challenge.tsx b/src/app/[pohid]/[chain]/[request]/Challenge.tsx index 1ed4569..83cb5eb 100644 --- a/src/app/[pohid]/[chain]/[request]/Challenge.tsx +++ b/src/app/[pohid]/[chain]/[request]/Challenge.tsx @@ -142,7 +142,7 @@ export default function Challenge({ header="Challenge" trigger={} > -
+
diff --git a/src/app/[pohid]/[chain]/[request]/Evidence.tsx b/src/app/[pohid]/[chain]/[request]/Evidence.tsx index 744c493..ddec5db 100644 --- a/src/app/[pohid]/[chain]/[request]/Evidence.tsx +++ b/src/app/[pohid]/[chain]/[request]/Evidence.tsx @@ -60,7 +60,7 @@ function Item({ index, uri, creationTime, sender }: ItemInterface) {
-
+
submitted by{" "} (function Evidence({ } > -
+
{policy && ( -
+
- - + + Registration Policy (at the time of submission) - + Updated:
@@ -195,7 +195,7 @@ export default withClientConnected(function Evidence({
setFile(acceptedFiles[0])} > diff --git a/src/app/[pohid]/[chain]/[request]/Info.tsx b/src/app/[pohid]/[chain]/[request]/Info.tsx index e05fd13..c865922 100644 --- a/src/app/[pohid]/[chain]/[request]/Info.tsx +++ b/src/app/[pohid]/[chain]/[request]/Info.tsx @@ -26,12 +26,12 @@ export default function Info({ nbRequests }: InfoProps) { height={128} width={128} /> -

+

The Proof of Humanity ID is a soulbound ID. It corresponds to each unique human registered on Proof of Humanity.

-

- This POH ID had {nbRequests} requests in total +

+ This POH ID had {nbRequests} requests claimed in this chain

); diff --git a/src/app/[pohid]/[chain]/[request]/page.tsx b/src/app/[pohid]/[chain]/[request]/page.tsx index 896a733..e3f883d 100644 --- a/src/app/[pohid]/[chain]/[request]/page.tsx +++ b/src/app/[pohid]/[chain]/[request]/page.tsx @@ -235,22 +235,23 @@ export default async function Request({ params }: PageProps) { } onChainVouches={onChainVouches} offChainVouches={offChainVouches} + arbitrationHistory={request.arbitratorHistory} /> -
+
{request.revocation && revocationFile && ( -
+
-
+
Revocation requested - {revocationFile.name} {revocationFile.fileURI && ( )}
-

{revocationFile.description}

+

{revocationFile.description}

- Requested by + Requested by -
+
{registrationFile && ( )} - + {/* {request.claimer.name} */} {registrationFile? registrationFile.name: ''} - + {registrationFile ? registrationFile.bio : ''}
-
@@ -300,7 +301,7 @@ export default async function Request({ params }: PageProps) {
{request.claimer.id.slice(0, 20)} @@ -308,10 +309,10 @@ export default async function Request({ params }: PageProps) { {request.claimer.id.slice(20)}
- + {chain.name} @@ -325,7 +326,7 @@ export default async function Request({ params }: PageProps) { height={24} width={24} /> - + {prettifyId(pohId).slice(0, 20)} {prettifyId(pohId).slice(20)} @@ -355,11 +356,11 @@ export default async function Request({ params }: PageProps) { /> )} - + {request.claimer.name} - + {registrationFile ? registrationFile.bio : ''}
@@ -377,9 +378,9 @@ export default async function Request({ params }: PageProps) {
-
+
Policy in force at submission
{/* (Policy in force since {new Date(policyUpdate * 1000).toDateString()}) */} @@ -408,7 +409,7 @@ export default async function Request({ params }: PageProps) {
)} {vourchesForData.find((v) => v) && ( -
+
Vouched for
{vourchesForData.map(async (vouch, idx) => { @@ -434,7 +435,7 @@ export default async function Request({ params }: PageProps) {
{vouchersData.find((v) => v) && ( -
+
Vouched by
{vouchersData.map(async (vouch, idx) => { @@ -458,7 +459,7 @@ export default async function Request({ params }: PageProps) {
)}
-
diff --git a/src/app/[pohid]/claim/Photo.tsx b/src/app/[pohid]/claim/Photo.tsx index 787370f..33ab38d 100644 --- a/src/app/[pohid]/claim/Photo.tsx +++ b/src/app/[pohid]/claim/Photo.tsx @@ -169,7 +169,7 @@ function Photo({ advance, photo$ }: PhotoProps) {
{ const file = received[0]; @@ -182,13 +182,13 @@ function Photo({ advance, photo$ }: PhotoProps) { }} disabled={!!originalPhoto} > -
+
Upload photo - + OR @@ -299,10 +299,10 @@ function Photo({ advance, photo$ }: PhotoProps) { {(showCamera || !!originalPhoto || !!photo) && ( )} diff --git a/src/app/[pohid]/claim/Review.tsx b/src/app/[pohid]/claim/Review.tsx index e445460..fb7df2d 100644 --- a/src/app/[pohid]/claim/Review.tsx +++ b/src/app/[pohid]/claim/Review.tsx @@ -53,13 +53,13 @@ function Review({
- + Registration Policy - + Updated:
@@ -118,7 +118,7 @@ function Review({
Initial deposit {balance && ( - + Your balance:{" "} {formatEth(balance.value)} {nativeCurrency.symbol} @@ -143,7 +143,7 @@ function Review({ of selfFunded$.set(formatEth(totalCost))} - className="mx-1 text-theme font-semibold underline underline-offset-2 cursor-pointer" + className="mx-1 text-orange font-semibold underline underline-offset-2 cursor-pointer" > {formatEther(totalCost)} {" "} diff --git a/src/app/[pohid]/claim/Video.tsx b/src/app/[pohid]/claim/Video.tsx index cb6d451..ef97e42 100644 --- a/src/app/[pohid]/claim/Video.tsx +++ b/src/app/[pohid]/claim/Video.tsx @@ -93,11 +93,11 @@ function VideoStep({ advance, video$, isRenewal }: PhotoProps) { {address} and say the phrase - " + " {phrase} - " + " @@ -112,7 +112,7 @@ function VideoStep({ advance, video$, isRenewal }: PhotoProps) { {!showCamera && !video && (
{ const file = received[0]; @@ -139,13 +139,13 @@ function VideoStep({ advance, video$, isRenewal }: PhotoProps) { }); }} > -
+
Upload video - + OR @@ -187,10 +187,10 @@ function VideoStep({ advance, video$, isRenewal }: PhotoProps) { {(showCamera || !!video) && ( )} diff --git a/src/app/[pohid]/page.tsx b/src/app/[pohid]/page.tsx index 5a79f0c..8ee3812 100644 --- a/src/app/[pohid]/page.tsx +++ b/src/app/[pohid]/page.tsx @@ -179,7 +179,7 @@ async function Profile({ params: { pohid } }: PageProps) {
-
+
poh id
- + {humanity[homeChain.id]!.humanity!.registration!.expirationTime < Date.now() / 1000 ? "Expired " @@ -234,7 +234,7 @@ async function Profile({ params: { pohid } }: PageProps) { pohId={pohId} /> ) : ( - + Renewal available{" "} {winnerClaimData.exists && (
-
+
{winnerClaimData.status !== "transferring"? 'Winning claim' : @@ -335,7 +335,7 @@ async function Profile({ params: { pohid } }: PageProps) { {pendingRequests.length > 0 && (
-
+
{pendingRequests.length} pending request {pendingRequests.length !== 1 && "s"}
@@ -371,7 +371,7 @@ async function Profile({ params: { pohid } }: PageProps) {
{pastRequests.length > 0 && ( <> -
+
{pastRequests.length} past request {pastRequests.length !== 1 && "s"}
diff --git a/src/app/attachment/Header.tsx b/src/app/attachment/Header.tsx index 4134518..c76212c 100644 --- a/src/app/attachment/Header.tsx +++ b/src/app/attachment/Header.tsx @@ -11,11 +11,11 @@ const Header: React.FC = () => { Paperclip Icon -

File

+

File

+ } > -
{children}
+
{children}
); diff --git a/src/components/Field.tsx b/src/components/Field.tsx index 3d24b79..dd241d9 100644 --- a/src/components/Field.tsx +++ b/src/components/Field.tsx @@ -22,7 +22,7 @@ function Field({ label, textarea = false, className, ...props }: FieldProps) { {textarea ? (