Skip to content

Commit

Permalink
Merge pull request #2196 from zeitgeistpm/tr-court-exit
Browse files Browse the repository at this point in the history
Implement court exiting
  • Loading branch information
yornaath authored Jan 30, 2024
2 parents a7dfc9a + a50887d commit 6ddf169
Show file tree
Hide file tree
Showing 5 changed files with 155 additions and 35 deletions.
135 changes: 135 additions & 0 deletions components/court/CourtExitButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
import { Dialog } from "@headlessui/react";
import { useQueryClient } from "@tanstack/react-query";
import { isRpcSdk, ZTG } from "@zeitgeistpm/sdk";
import { toMs } from "@zeitgeistpm/utility/dist/time";
import Modal from "components/ui/Modal";
import TransactionButton from "components/ui/TransactionButton";
import { useConnectedCourtParticipant } from "lib/hooks/queries/court/useConnectedCourtParticipant";
import { courtParticipantsRootKey } from "lib/hooks/queries/court/useCourtParticipants";
import { useChainConstants } from "lib/hooks/queries/useChainConstants";
import { useExtrinsic } from "lib/hooks/useExtrinsic";
import { useSdkv2 } from "lib/hooks/useSdkv2";
import { useChainTime } from "lib/state/chaintime";
import { useNotifications } from "lib/state/notifications";
import { useWallet } from "lib/state/wallet";
import moment from "moment";
import { useMemo, useState } from "react";

const CourtExitButton = ({ className }: { className?: string }) => {
const [isOpen, setIsOpen] = useState(false);
const { data: constants } = useChainConstants();
const [sdk, id] = useSdkv2();
const notificationStore = useNotifications();
const wallet = useWallet();
const participant = useConnectedCourtParticipant();
const queryClient = useQueryClient();
const time = useChainTime();

const {
isLoading: isLeaveLoading,
send: leaveCourt,
fee,
} = useExtrinsic(
() => {
if (!isRpcSdk(sdk) || !wallet.realAddress) return;
return sdk.api.tx.court.exitCourt(wallet.realAddress);
},
{
onSuccess: () => {
queryClient.invalidateQueries([id, courtParticipantsRootKey]);
notificationStore.pushNotification("Successfully exited court", {
type: "Success",
});
},
},
);

const cooldownTime = useMemo(() => {
if (time && participant && constants && participant?.prepareExitAt) {
const preparedExitAt = participant?.prepareExitAt;
const canExitAt = preparedExitAt + constants?.court.inflationPeriodBlocks;

return {
total: toMs(time, {
start: 0,
end: constants?.court.inflationPeriodBlocks,
}),
left: toMs(time, { start: time?.block, end: canExitAt }),
};
}

return null;
}, [time, participant, constants]);

const canExit = !Boolean(cooldownTime?.left && cooldownTime?.left > 0);

const percentage = cooldownTime
? 100 - (cooldownTime?.left / cooldownTime?.total) * 100
: null;

return (
<>
<button
className={`rounded-md ${
canExit ? "bg-[#DC056C]" : "bg-gray-400"
} px-4 py-2 text-white ${className}`}
onClick={() => setIsOpen(true)}
disabled={!canExit}
>
<div className="flex items-center gap-1">
{canExit ? "Exit" : "Preparing to exit"}
{!canExit && (
<span className="text-xs text-gray-500">
({moment.duration(cooldownTime?.left).humanize()} left)
</span>
)}
</div>
{!canExit && (
<div className="mb-1 w-full">
<div className="h-[3px] w-full rounded-lg bg-gray-100 bg-opacity-25">
<div
className={`h-full rounded-lg bg-blue-400 transition-all`}
style={{ width: `${percentage}%` }}
/>
</div>
</div>
)}
</button>
<Modal open={isOpen} onClose={() => setIsOpen(false)}>
<Dialog.Panel className="w-full max-w-[462px] rounded-[10px] bg-white p-[30px]">
<h3 className="mb-8">Exit Court</h3>
<div className="mb-3 flex flex-col">
<div className="flex">
<div className="mr-auto">Stake:</div>
<div>
{participant?.stake.div(ZTG).toNumber()}{" "}
{constants?.tokenSymbol}
</div>
</div>
</div>
<p className="mb-3 text-center text-sm text-gray-500">
By confirming exit you will leave the court, your stake will be
unlocked and moved back to your free balance.
</p>
<div className="mt-[20px] flex w-full flex-col items-center gap-8 text-ztg-18-150 font-semibold">
<div className="center mb-[10px] text-ztg-12-120 font-normal text-sky-600">
<span className="ml-1 text-black">
Network Fee: {fee ? fee.amount.div(ZTG).toFixed(3) : 0}{" "}
{fee?.symbol}
</span>
</div>
<TransactionButton
className="w-full max-w-[250px]"
disabled={isLeaveLoading}
onClick={() => leaveCourt()}
>
Confirm Exit
</TransactionButton>
</div>
</Dialog.Panel>
</Modal>
</>
);
};

export default CourtExitButton;
3 changes: 2 additions & 1 deletion components/court/JurorsTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import DelegateButton from "./DelegateButton";
import { useCourtParticipants } from "lib/hooks/queries/court/useCourtParticipants";
import Decimal from "decimal.js";
import { ZTG } from "@zeitgeistpm/sdk";
import { isNumber } from "lodash-es";

const columns: TableColumn[] = [
{
Expand Down Expand Up @@ -68,7 +69,7 @@ const JurorsTable = () => {
),
personalStake: juror.stake.div(ZTG).toNumber(),
totalStake: juror.stake.plus(delegatorStake).div(ZTG).toNumber(),
status: juror.prepareExit ? "Exiting" : "Active",
status: isNumber(juror.prepareExitAt) ? "Exiting" : "Active",
delegators: delegators.length,
button: <DelegateButton address={juror.address} />,
};
Expand Down
2 changes: 1 addition & 1 deletion components/court/ManageDelegationsForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ const ManageDelegationsForm = (props: ManageDelegationsFormProps) => {
{...register("percentage", { value: "0" })}
/>

<div className="subtle-scroll-bar max-h-[400px] w-full overflow-y-scroll">
<div className="subtle-scroll-bar max-h-[300px] w-full overflow-y-scroll lg:max-h-[400px]">
<div className="mb-2 flex items-center text-sm">
<h3 className="flex-1 text-base">Juror</h3>
<h3 className="text-xs">Delegated</h3>
Expand Down
4 changes: 3 additions & 1 deletion lib/hooks/queries/court/useCourtParticipants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,9 @@ export const useCourtParticipants = () => {
return {
address: (address.toHuman() as [string])[0],
stake: new Decimal(unwrappedDetails?.stake.toString() ?? 0),
prepareExit: unwrappedDetails?.prepareExitAt.isSome,
prepareExitAt: unwrappedDetails?.prepareExitAt
.unwrapOr(null)
?.toNumber(),
type: unwrappedDetails?.delegations.isSome
? ("Delegator" as const)
: ("Juror" as const),
Expand Down
46 changes: 14 additions & 32 deletions pages/court/index.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,15 @@
import { Disclosure } from "@headlessui/react";
import { useQueryClient } from "@tanstack/react-query";
import { ZTG, isRpcSdk } from "@zeitgeistpm/sdk";
import { ZTG } from "@zeitgeistpm/sdk";
import { CourtCasesTable } from "components/court/CourtCasesTable";
import CourtExitButton from "components/court/CourtExitButton";
import CourtUnstakeButton from "components/court/CourtUnstakeButton";
import JoinCourtAsJurorButton from "components/court/JoinCourtAsJurorButton";
import ManageDelegationButton from "components/court/ManageDelegationButton";
import CourtUnstakeButton from "components/court/CourtUnstakeButton";
import InfoPopover from "components/ui/InfoPopover";
import { useConnectedCourtParticipant } from "lib/hooks/queries/court/useConnectedCourtParticipant";
import { useCourtCases } from "lib/hooks/queries/court/useCourtCases";
import {
courtParticipantsRootKey,
useCourtParticipants,
} from "lib/hooks/queries/court/useCourtParticipants";
import { useCourtParticipants } from "lib/hooks/queries/court/useCourtParticipants";
import { useCourtStakeSharePercentage } from "lib/hooks/queries/court/useCourtStakeSharePercentage";
import { useCourtTotalStakedAmount } from "lib/hooks/queries/court/useCourtTotalStakedAmount";
import {
Expand All @@ -20,11 +18,11 @@ import {
} from "lib/hooks/queries/court/useCourtYearlyInflation";
import { useChainConstants } from "lib/hooks/queries/useChainConstants";
import { useZtgPrice } from "lib/hooks/queries/useZtgPrice";
import { useExtrinsic } from "lib/hooks/useExtrinsic";
import { useSdkv2 } from "lib/hooks/useSdkv2";
import { useNotifications } from "lib/state/notifications";
import { useWallet } from "lib/state/wallet";
import { formatNumberLocalized } from "lib/util";
import { isNumber } from "lodash-es";
import { NextPage } from "next";
import Image from "next/image";
import Link from "next/link";
Expand Down Expand Up @@ -55,31 +53,10 @@ const CourtPage: NextPage = ({
}

const { data: constants } = useChainConstants();
const [sdk, id] = useSdkv2();
const notificationStore = useNotifications();
const wallet = useWallet();
const queryClient = useQueryClient();

const connectedParticipant = useConnectedCourtParticipant();
const { data: ztgPrice } = useZtgPrice();

const stakeShare = useCourtStakeSharePercentage();

const { isLoading: isLeaveLoading, send: leaveCourt } = useExtrinsic(
() => {
if (!isRpcSdk(sdk) || !wallet.realAddress) return;
return sdk.api.tx.court.exitCourt(wallet.realAddress); //todo: is this correct input?
},
{
onSuccess: () => {
queryClient.invalidateQueries([id, courtParticipantsRootKey]);
notificationStore.pushNotification("Successfully exited court", {
type: "Success",
});
},
},
);

return (
<div className="mt-4 flex flex-col gap-y-4">
<div className="relative mb-4 basis-7 flex-wrap items-center gap-4 lg:flex">
Expand Down Expand Up @@ -175,12 +152,17 @@ const CourtPage: NextPage = ({

<div>
<div className="mb-3 flex flex-col gap-4 md:flex-row">
<JoinCourtAsJurorButton className="w-full md:w-auto" />
<ManageDelegationButton className="w-full md:w-auto" />
<JoinCourtAsJurorButton className="h-full w-full md:w-auto" />
<ManageDelegationButton className="h-full w-full md:w-auto" />

{connectedParticipant &&
!isNumber(connectedParticipant.prepareExitAt) && (
<CourtUnstakeButton className="h-full w-full md:w-auto" />
)}

{connectedParticipant &&
!connectedParticipant?.prepareExit && (
<CourtUnstakeButton className="w-full md:w-auto" />
isNumber(connectedParticipant.prepareExitAt) && (
<CourtExitButton className="h-full w-full md:w-auto" />
)}
</div>
</div>
Expand Down

0 comments on commit 6ddf169

Please sign in to comment.