Skip to content

Commit

Permalink
Merge branch 'staging' of https://github.com/zeitgeistpm/ui into tr-m…
Browse files Browse the repository at this point in the history
…ore-asset-refactor
  • Loading branch information
Robiquet committed May 13, 2024
2 parents ebc3d4d + 331cec0 commit e61a086
Show file tree
Hide file tree
Showing 15 changed files with 801 additions and 153 deletions.
6 changes: 3 additions & 3 deletions components/court/CourtExitButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -71,13 +71,13 @@ const CourtExitButton = ({ className }: { className?: string }) => {
<>
<button
className={`rounded-md ${
canExit ? "bg-[#DC056C]" : "bg-gray-400"
canExit ? "bg-[#670031]" : "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"}
<div className="flex items-center justify-center gap-1">
<span>{canExit ? "Exit" : "Preparing to exit"}</span>
{!canExit && (
<span className="text-xs text-gray-500">
({moment.duration(cooldownTime?.left).humanize()} left)
Expand Down
2 changes: 1 addition & 1 deletion components/court/CourtUnstakeButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ const CourtUnstakeButton = ({ className }: { className?: string }) => {
return (
<>
<button
className={`rounded-md bg-[#DC056C] px-4 py-2 text-white ${className}`}
className={`rounded-md bg-[#670031] px-4 py-2 text-white ${className}`}
onClick={() => setIsOpen(true)}
>
Unstake
Expand Down
2 changes: 1 addition & 1 deletion components/court/JoinCourtAsJurorButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ const JoinCourtAsJurorButton = ({ className }: { className?: string }) => {
<div className="relative">
<button
disabled={isLoading}
className={`rounded-md bg-[#670031] px-4 py-2 text-white transition-all ${
className={`rounded-md bg-[#DC056C] px-4 py-2 text-white transition-all ${
connectedParticipant?.type === "Delegator" &&
"ring-2 ring-orange-500"
} ${className}`}
Expand Down
2 changes: 1 addition & 1 deletion components/court/ManageDelegationButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ const ManageDelegationButton = ({ className }: { className?: string }) => {
<>
<div className="relative">
<button
className={`rounded-md bg-[#670031] px-4 py-2 text-white transition-all ${
className={`rounded-md bg-[#DC056C] px-4 py-2 text-white transition-all ${
connectedParticipant?.type === "Juror" && "ring-2 ring-orange-500"
} ${className}`}
onClick={() => setIsOpen(true)}
Expand Down
140 changes: 140 additions & 0 deletions components/portfolio/CourtRewardsTable.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
import SubScanIcon from "components/icons/SubScanIcon";
import Table, { TableColumn, TableData } from "components/ui/Table";
import Decimal from "decimal.js";
import { ZTG } from "lib/constants";
import { useChainConstants } from "lib/hooks/queries/useChainConstants";
import { useMintedInCourt } from "lib/hooks/queries/useMintedInCourt";
import { useZtgPrice } from "lib/hooks/queries/useZtgPrice";
import { formatNumberLocalized } from "lib/util";
import EmptyPortfolio from "./EmptyPortfolio";
import {
isPayoutEligible,
useCourtNextPayout,
} from "lib/hooks/queries/useCourtNextPayout";
import { times } from "lodash-es";
import { isNotNull } from "@zeitgeistpm/utility/dist/null";
import InfoPopover from "components/ui/InfoPopover";
import { PiTimerBold } from "react-icons/pi";

const columns: TableColumn[] = [
{
header: "Time",
accessor: "timestamp",
type: "component",
},
{
header: "Amount",
accessor: "amount",
type: "component",
alignment: "right",
},
{
header: "Subscan",
accessor: "subscan",
type: "component",
width: "100px",
},
];

const CourtRewardsTable = ({ address }: { address: string }) => {
const { data: mintedInCourt, isLoading } = useMintedInCourt({
account: address,
});
const { data: ztgPrice } = useZtgPrice();
const { data: constants } = useChainConstants();

const { data: courtPayout } = useCourtNextPayout();

let tableData: TableData[] | undefined = mintedInCourt?.map((mint) => {
return {
timestamp: (
<span>
{new Intl.DateTimeFormat("default", {
dateStyle: "medium",
timeStyle: "medium",
}).format(new Date(mint?.timestamp))}
</span>
),
amount: (
<div>
<div>
{formatNumberLocalized(
new Decimal(mint?.dBalance ?? 0).div(ZTG).toNumber(),
)}{" "}
<b>{constants?.tokenSymbol}</b>
</div>
<div className="text-gray-400">
${" "}
{formatNumberLocalized(
ztgPrice
?.mul(mint?.dBalance ?? 0)
.div(ZTG)
.toNumber() ?? 0,
)}
</div>
</div>
),
subscan: (
<a
className="center text-sm"
target="_blank"
referrerPolicy="no-referrer"
rel="noopener"
href={`https://zeitgeist.subscan.io/block/${mint?.blockNumber}?tab=event`}
>
<div className="">
<SubScanIcon />
</div>
</a>
),
};
});

tableData = [
isPayoutEligible(courtPayout)
? {
timestamp: (
<span className="flex items-center gap-2 text-gray-400">
{new Intl.DateTimeFormat("default", {
dateStyle: "medium",
timeStyle: "medium",
}).format(courtPayout.nextRewardDate)}
<span className="flex items-center gap-1 italic">
(ETA <PiTimerBold size={18} />)
</span>
</span>
),
amount: <div className="text-gray-300">--</div>,
subscan: (
<div className="center text-center">
<InfoPopover
icon={<PiTimerBold className="text-orange-300" size={24} />}
position={"top-start"}
>
Next expected staking reward payout.
</InfoPopover>
</div>
),
}
: null,
...(tableData ?? []),
].filter(isNotNull);

return (
<div>
{isLoading === false &&
(mintedInCourt == null || mintedInCourt?.length === 0) ? (
<EmptyPortfolio
headerText="No Court Rewards"
bodyText=""
buttonText="Go To Court"
buttonLink="/court"
/>
) : (
<Table columns={columns} data={tableData} />
)}
</div>
);
};

export default CourtRewardsTable;
32 changes: 32 additions & 0 deletions components/portfolio/CourtTabGroup.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { Tab } from "@headlessui/react";
import SubTabsList from "components/ui/SubTabsList";
import { useQueryParamState } from "lib/hooks/useQueryParamState";
import CourtRewardsTable from "./CourtRewardsTable";

type CourtGroupItem = "Rewards";
const courtTabItems: CourtGroupItem[] = ["Rewards"];

const CourtTabGroup = ({ address }: { address: string }) => {
const [historyTabSelection, setHistoryTabSelection] =
useQueryParamState<CourtGroupItem>("courtTab");

const courtTabIndex = courtTabItems.indexOf(historyTabSelection);
const selectedIndex = courtTabIndex !== -1 ? courtTabIndex : 0;

return (
<Tab.Group
defaultIndex={0}
selectedIndex={selectedIndex}
onChange={(index) => setHistoryTabSelection(courtTabItems[index])}
>
<SubTabsList titles={courtTabItems} />
<Tab.Panels>
<Tab.Panel>
<CourtRewardsTable address={address} />
</Tab.Panel>
</Tab.Panels>
</Tab.Group>
);
};

export default CourtTabGroup;
2 changes: 1 addition & 1 deletion components/ui/InfoPopover.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ export const InfoPopover: React.FC<InfoPopoverProps> = ({
<Popover.Panel
className={`absolute z-[100] bg-tooltip-bg ${positionCss} w-screen rounded-md lg:w-[564px] ${popoverCss}`}
>
<div className="shadow-xs overflow-hidden rounded-md p-5 text-left text-base font-light text-black ring-2 ring-orange-400 ring-opacity-20">
<div className="shadow-xs overflow-hidden rounded-md px-3 py-2 text-left text-sm font-light text-black ring-2 ring-orange-400 ring-opacity-20">
{children}
</div>
</Popover.Panel>
Expand Down
2 changes: 1 addition & 1 deletion lib/constants/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ export const endpoints: EndpointOption[] = [

export const graphQlEndpoints: EndpointOption[] = [
{
value: "https://zeitgeist-squid-bsr.stellate.sh/",
value: "https://processor.bsr.zeitgeist.pm/graphql",
label: "Battery Park (Testnet)",
environment: "staging",
},
Expand Down
146 changes: 146 additions & 0 deletions lib/hooks/queries/useCourtNextPayout.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
import { useQuery } from "@tanstack/react-query";
import { HistoricalAccountBalanceOrderByInput } from "@zeitgeistpm/indexer";
import { IndexerContext, isIndexedSdk, Sdk } from "@zeitgeistpm/sdk";
import { blockDate } from "@zeitgeistpm/utility/dist/time";
import Decimal from "decimal.js";
import { useChainTime } from "lib/state/chaintime";
import { useWallet } from "lib/state/wallet";
import { useSdkv2 } from "../useSdkv2";
import { useChainConstants } from "./useChainConstants";

export const courtNextPayoutRootKey = "court-next-payout";

export type CourtPayoutInfo = {
inflationPeriod: number;
nextPayoutBlock: number;
lastPayoutBlock: number;
nextPayoutDate: Date;
lastPayoutDate: Date;
};

export type WithPayoutEligibility = CourtPayoutInfo & {
nextRewardBlock: number;
nextRewardDate: Date;
};

export const isPayoutEligible = (
info?: CourtPayoutInfo | WithPayoutEligibility | null,
): info is WithPayoutEligibility =>
(info as WithPayoutEligibility)?.nextRewardBlock !== undefined;

export const useCourtNextPayout = () => {
const [sdk, id] = useSdkv2();
const now = useChainTime();
const { data: constants } = useChainConstants();
const wallet = useWallet();

const enabled = isIndexedSdk(sdk) && now && constants && wallet.realAddress;

const query = useQuery<CourtPayoutInfo | WithPayoutEligibility | null>(
[id, courtNextPayoutRootKey, wallet?.realAddress, now?.block],
async () => {
if (enabled) {
/**
* @note
* last_payout_block(current_block) = floor(current_block / inf_per) * inf_per
* next_payout_block(current_block) = last_payout_block(current_block) + inf_per
*/

const currentBlock = new Decimal(now.block);

const inflationPeriod = new Decimal(
constants.court.inflationPeriodBlocks,
);

const lastPayoutBlock = new Decimal(currentBlock)
.div(inflationPeriod)
.floor()
.mul(inflationPeriod);

const nextPayoutBlock = lastPayoutBlock.add(inflationPeriod);

const participantFirstJoinedAt = await getAccountJoined(
sdk,
wallet.realAddress!,
);

const courtPayoutInfo: CourtPayoutInfo = {
inflationPeriod: inflationPeriod.toNumber(),
nextPayoutBlock: nextPayoutBlock.toNumber(),
lastPayoutBlock: lastPayoutBlock.toNumber(),
nextPayoutDate: blockDate(now, nextPayoutBlock.toNumber()),
lastPayoutDate: blockDate(now, lastPayoutBlock.toNumber()),
};

if (participantFirstJoinedAt) {
const withPayoutEligibility: WithPayoutEligibility = {
...courtPayoutInfo,
nextRewardBlock: nextPayoutBlock.toNumber(),
nextRewardDate: blockDate(now, nextPayoutBlock.toNumber()),
};

return withPayoutEligibility;
}

return courtPayoutInfo;
}

return null;
},
{
enabled: Boolean(enabled),
keepPreviousData: true,
},
);

return query;
};

const getAccountJoined = async (sdk: Sdk<IndexerContext>, address: string) => {
const { historicalAccountBalances } =
await sdk.indexer.historicalAccountBalances({
where: {
AND: [
{ accountId_eq: address },
{
OR: [
{
extrinsic: {
name_eq: "Court.join_court",
},
},
{
extrinsic: {
name_eq: "Court.delegate",
},
},

{
extrinsic: {
name_eq: "Court.exit_court",
},
},
],
},
],
},
order: HistoricalAccountBalanceOrderByInput.BlockNumberDesc,
});

let earliestEligibleJoin: Decimal | null = null;

for (const event of historicalAccountBalances) {
if (
event.extrinsic?.name === "Court.join_court" ||
event.extrinsic?.name === "Court.delegate"
) {
earliestEligibleJoin = new Decimal(event.blockNumber);
}
if (event.extrinsic?.name === "Court.exit_court") {
console.log("EXITED");
break;
}
}

return earliestEligibleJoin;
};
Loading

0 comments on commit e61a086

Please sign in to comment.