-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Browse files
Browse the repository at this point in the history
#1954 Court
- Loading branch information
Showing
69 changed files
with
4,039 additions
and
2,759 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
import { useQueryClient } from "@tanstack/react-query"; | ||
import { isRpcSdk } from "@zeitgeistpm/sdk"; | ||
import TransactionButton from "components/ui/TransactionButton"; | ||
import { courtCasesRootKey } from "lib/hooks/queries/court/useCourtCases"; | ||
import { useExtrinsic } from "lib/hooks/useExtrinsic"; | ||
import { useSdkv2 } from "lib/hooks/useSdkv2"; | ||
|
||
export const CourtAppealForm = ({ caseId }: { caseId: number }) => { | ||
const [sdk, id] = useSdkv2(); | ||
const queryClient = useQueryClient(); | ||
|
||
const { send, isReady, isLoading, isBroadcasting } = useExtrinsic( | ||
() => { | ||
if (isRpcSdk(sdk)) { | ||
return sdk.api.tx.court.appeal(caseId); | ||
} | ||
return undefined; | ||
}, | ||
{ | ||
onSuccess: () => { | ||
queryClient.invalidateQueries([id, courtCasesRootKey]); | ||
}, | ||
}, | ||
); | ||
|
||
return ( | ||
<div className="overflow-hidden rounded-xl shadow-lg"> | ||
<div className="center flex bg-fog-of-war py-3"> | ||
<h3 className="text-gray-300 text-opacity-50">Appeal Court</h3> | ||
</div> | ||
|
||
<div className="px-2 py-6 text-center"> | ||
<div className="mb-4"> | ||
<div className="mb-3 text-sm text-gray-700"> | ||
If you think the court has made a mistake, you can appeal the | ||
decision. This will start a new round of voting. | ||
</div> | ||
</div> | ||
|
||
<TransactionButton | ||
disabled={!isReady || isLoading || isBroadcasting} | ||
className={`relative h-[56px] ${ | ||
isLoading && "animate-pulse" | ||
} !bg-orange-400`} | ||
type="submit" | ||
loading={isLoading || isBroadcasting} | ||
onClick={() => send()} | ||
> | ||
<div> | ||
<div className="center h-[20px] font-normal">Submit Appeal</div> | ||
</div> | ||
</TransactionButton> | ||
</div> | ||
</div> | ||
); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,248 @@ | ||
import { ZrmlCourtCourtInfo } from "@polkadot/types/lookup"; | ||
import { isInfinity } from "@zeitgeistpm/utility/dist/infinity"; | ||
import { blockDate } from "@zeitgeistpm/utility/dist/time"; | ||
import InfoPopover from "components/ui/InfoPopover"; | ||
import Skeleton from "components/ui/Skeleton"; | ||
import Table, { TableColumn, TableData } from "components/ui/Table"; | ||
import { useCaseMarketId } from "lib/hooks/queries/court/useCaseMarketId"; | ||
import { CourtCaseInfo } from "lib/hooks/queries/court/useCourtCase"; | ||
import { useCourtCases } from "lib/hooks/queries/court/useCourtCases"; | ||
import { useVoteDrawsForCase } from "lib/hooks/queries/court/useVoteDraws"; | ||
import { useMarket } from "lib/hooks/queries/useMarket"; | ||
import { useChainTime } from "lib/state/chaintime"; | ||
import { CourtStage, getCourtStage } from "lib/state/court/get-stage"; | ||
import { useWallet } from "lib/state/wallet"; | ||
import Link from "next/link"; | ||
import { useMemo } from "react"; | ||
import { AiOutlineEye } from "react-icons/ai"; | ||
import { LuVote } from "react-icons/lu"; | ||
import { courtStageCopy } from "./CourtStageTimer"; | ||
|
||
const columns: TableColumn[] = [ | ||
{ | ||
header: "#", | ||
accessor: "id", | ||
type: "text", | ||
}, | ||
{ | ||
header: "Case", | ||
accessor: "case", | ||
type: "component", | ||
}, | ||
{ | ||
header: "Status", | ||
accessor: "status", | ||
type: "component", | ||
}, | ||
{ | ||
header: "Voting Ends", | ||
accessor: "ends", | ||
type: "text", | ||
}, | ||
{ | ||
header: "", | ||
accessor: "actions", | ||
type: "component", | ||
}, | ||
]; | ||
|
||
export const CourtCasesTable = () => { | ||
const { data: cases } = useCourtCases(); | ||
const time = useChainTime(); | ||
|
||
cases?.sort((a, b) => { | ||
if (b.case.status.type === "Reassigned") return -1; | ||
return a.case.roundEnds.vote.toNumber() > b.case.roundEnds.vote.toNumber() | ||
? 1 | ||
: 0; | ||
}); | ||
|
||
const tableData: TableData[] | undefined = cases?.map((courtCase) => { | ||
return { | ||
id: `${courtCase.id}`, | ||
case: <CaseNameForCaseId id={courtCase.id} />, | ||
status: <CaseStatus courtCase={courtCase} />, | ||
ends: | ||
time && | ||
new Intl.DateTimeFormat("default", { | ||
dateStyle: "medium", | ||
timeStyle: "short", | ||
}).format(blockDate(time, courtCase.case.roundEnds.vote.toNumber())), | ||
actions: <CaseActions caseId={courtCase.id} courtCase={courtCase.case} />, | ||
}; | ||
}); | ||
|
||
return ( | ||
<div className="relative"> | ||
<Table columns={columns} data={tableData} /> | ||
</div> | ||
); | ||
}; | ||
|
||
const CaseNameForCaseId = (props: { id: number }) => { | ||
const { data: marketId } = useCaseMarketId(props.id); | ||
const { data: market } = useMarket({ marketId: marketId! }); | ||
return ( | ||
<> | ||
{market ? ( | ||
<div className="text-sm">{market?.question}</div> | ||
) : ( | ||
<Skeleton /> | ||
)} | ||
</> | ||
); | ||
}; | ||
|
||
const CaseStatus = ({ courtCase }: { courtCase: CourtCaseInfo }) => { | ||
const { data: marketId } = useCaseMarketId(courtCase.id); | ||
const { data: market } = useMarket({ marketId: marketId! }); | ||
const chainTime = useChainTime(); | ||
|
||
const stage = useMemo(() => { | ||
if (market && chainTime) { | ||
return getCourtStage(chainTime, market, courtCase.case); | ||
} | ||
}, [chainTime, market]); | ||
|
||
const percentage = | ||
stage && isInfinity(stage.remainingBlocks) | ||
? 100 | ||
: stage | ||
? ((stage.totalTime - stage.remainingBlocks) / stage.totalTime) * 100 | ||
: 0; | ||
|
||
return ( | ||
<div className=""> | ||
{stage ? ( | ||
<> | ||
<div className="mb-1 flex items-center gap-2"> | ||
<div className={`${caseStatusCopy[stage.type].color}`}> | ||
{caseStatusCopy[stage.type].title} | ||
</div> | ||
<InfoPopover position="top"> | ||
{caseStatusCopy[stage.type].description} | ||
</InfoPopover> | ||
</div> | ||
|
||
<div className="w-full"> | ||
<div className="h-1 w-full rounded-lg bg-gray-100"> | ||
<div | ||
className={`h-full rounded-lg transition-all ${ | ||
courtStageCopy[stage.type].color | ||
}`} | ||
style={{ width: `${percentage}%` }} | ||
/> | ||
</div> | ||
</div> | ||
</> | ||
) : ( | ||
<Skeleton /> | ||
)} | ||
</div> | ||
); | ||
}; | ||
|
||
const CaseActions = ({ | ||
caseId, | ||
courtCase, | ||
}: { | ||
caseId: number; | ||
courtCase: ZrmlCourtCourtInfo; | ||
}) => { | ||
const wallet = useWallet(); | ||
|
||
const { data: marketId } = useCaseMarketId(caseId); | ||
const { data: market } = useMarket({ marketId: marketId! }); | ||
const chainTime = useChainTime(); | ||
|
||
const stage = useMemo(() => { | ||
if (market && chainTime) { | ||
return getCourtStage(chainTime, market, courtCase); | ||
} | ||
}, [chainTime, market]); | ||
|
||
const { data: draws } = useVoteDrawsForCase(caseId); | ||
|
||
const connectedParticipantDraw = draws?.find( | ||
(draw) => draw.courtParticipant.toString() === wallet.realAddress, | ||
); | ||
|
||
const canVote = useMemo(() => { | ||
return stage?.type === "vote" && connectedParticipantDraw?.vote.isDrawn; | ||
}, [stage, connectedParticipantDraw]); | ||
|
||
const canReveal = useMemo(() => { | ||
return ( | ||
stage?.type === "aggregation" && connectedParticipantDraw?.vote.isSecret | ||
); | ||
}, [stage, connectedParticipantDraw]); | ||
|
||
return ( | ||
<div className="flex w-full items-center justify-center"> | ||
<Link href={`/court/${caseId}`}> | ||
<button | ||
className={` | ||
center line-clamp-1 gap-3 self-end rounded-full border-2 border-gray-300 px-5 py-1.5 text-xs hover:border-gray-400 disabled:opacity-50 md:min-w-[220px] | ||
${canVote && "animate-pulse border-ztg-blue bg-ztg-blue text-white"} | ||
${ | ||
canReveal && | ||
"animate-pulse border-purple-500 bg-purple-500 text-white" | ||
} | ||
`} | ||
> | ||
{canVote ? ( | ||
<> | ||
<LuVote size={18} /> <span>Vote</span> | ||
</> | ||
) : canReveal ? ( | ||
<> | ||
<AiOutlineEye size={18} /> <span>Reveal Vote</span> | ||
</> | ||
) : ( | ||
"View Case" | ||
)} | ||
</button> | ||
</Link> | ||
</div> | ||
); | ||
}; | ||
|
||
const caseStatusCopy: Record< | ||
CourtStage["type"], | ||
{ | ||
title: string; | ||
description: string; | ||
color: string; | ||
} | ||
> = { | ||
"pre-vote": { | ||
title: "Pre-Vote", | ||
description: "Waiting for the vote period to start.", | ||
color: "text-gray-400", | ||
}, | ||
vote: { | ||
title: "Vote", | ||
description: "Case is now open for voting by jurors.", | ||
color: "text-blue-400", | ||
}, | ||
aggregation: { | ||
title: "Aggregation", | ||
description: "Votes can now be revealed by jurors.", | ||
color: "text-purple-400", | ||
}, | ||
appeal: { | ||
title: "Appeal", | ||
description: "Jurors can now appeal the voted outcome.", | ||
color: "text-orange-400", | ||
}, | ||
reassigned: { | ||
title: "Reassigned", | ||
description: "Case has been reassigned and winners paid out.", | ||
color: "text-gray-400", | ||
}, | ||
closed: { | ||
title: "Closed", | ||
description: "Case has been closed. Waiting to be reassigned.", | ||
color: "text-gray-400", | ||
}, | ||
}; |
Oops, something went wrong.