From 26e8614dbf045c38ba4fa9377b2026a7ad022093 Mon Sep 17 00:00:00 2001 From: leochen Date: Fri, 28 Feb 2025 17:26:48 +0800 Subject: [PATCH] Support cleanup poll for fellowship referenda, #5540 (#5549) * init FellowshipReferendumCleanupPoll component, #5540 * add context for CleanupPollButton, #5540 * add tx func, #5540 * chore: optimize fellowship core members list style, #5540 * optimize null guard, #5540 * optimize context & components, #5540 * rename, #5540 * add onInblock toast, #5540 * rename, #5540 * add sign popup, #5540 * use SignerWithBalance, #5540 * Improve feedback text, #5540 --------- Co-authored-by: Yongfeng LI --- .../core/member/avatarAndAddress.js | 1 - .../referenda/cleanupPoll/index.jsx | 62 +++++++++++++++ .../referenda/cleanupPoll/popup.jsx | 60 +++++++++++++++ .../pages/fellowship/memberListView.js | 4 +- .../context/fellowship/referendumVoting.js | 75 +++++++++++++++++++ .../fellowship/referendum/sidebar/index.js | 2 + 6 files changed, 201 insertions(+), 3 deletions(-) create mode 100644 packages/next-common/components/fellowship/referenda/cleanupPoll/index.jsx create mode 100644 packages/next-common/components/fellowship/referenda/cleanupPoll/popup.jsx create mode 100644 packages/next-common/context/fellowship/referendumVoting.js diff --git a/packages/next-common/components/collectives/core/member/avatarAndAddress.js b/packages/next-common/components/collectives/core/member/avatarAndAddress.js index 95b609b6dd..b8295e189a 100644 --- a/packages/next-common/components/collectives/core/member/avatarAndAddress.js +++ b/packages/next-common/components/collectives/core/member/avatarAndAddress.js @@ -42,7 +42,6 @@ export function AvatarAndAddressInListView({ address, isActive }) { diff --git a/packages/next-common/components/fellowship/referenda/cleanupPoll/index.jsx b/packages/next-common/components/fellowship/referenda/cleanupPoll/index.jsx new file mode 100644 index 0000000000..ed28774e73 --- /dev/null +++ b/packages/next-common/components/fellowship/referenda/cleanupPoll/index.jsx @@ -0,0 +1,62 @@ +import Button from "next-common/lib/button"; +import { useState, useMemo } from "react"; +import Tooltip from "next-common/components/tooltip"; +import ReferendumVotingProvider, { + useReferendumVoting, +} from "next-common/context/fellowship/referendumVoting"; +import useRealAddress from "next-common/utils/hooks/useRealAddress"; +import { useReferendumVotingFinishIndexer } from "next-common/context/post/referenda/useReferendumVotingFinishHeight"; +import { isNil } from "lodash-es"; +import { useOnchainData } from "next-common/context/post"; +import dynamicPopup from "next-common/lib/dynamic/popup"; + +const CleanupPopup = dynamicPopup(() => import("./popup")); + +function CleanupPollButton() { + const { votes, isLoading } = useReferendumVoting(); + const address = useRealAddress(); + const [showPopup, setShowPopup] = useState(false); + + const showCleanup = useMemo(() => { + return votes?.length > 0 && !isLoading && address; + }, [votes, isLoading, address]); + + if (!showCleanup) { + return null; + } + + return ( + <> + + + + {showPopup && setShowPopup(false)} />} + + ); +} + +function MakesureReferendumFinishedGuard({ children }) { + const finishedIndexer = useReferendumVotingFinishIndexer(); + const { referendumIndex } = useOnchainData(); + + if (isNil(finishedIndexer) || isNil(referendumIndex)) { + return null; + } + + return children; +} + +export default function FellowshipReferendumCleanupPoll() { + return ( + + + + + + ); +} diff --git a/packages/next-common/components/fellowship/referenda/cleanupPoll/popup.jsx b/packages/next-common/components/fellowship/referenda/cleanupPoll/popup.jsx new file mode 100644 index 0000000000..1cd899b466 --- /dev/null +++ b/packages/next-common/components/fellowship/referenda/cleanupPoll/popup.jsx @@ -0,0 +1,60 @@ +import TxSubmissionButton from "next-common/components/common/tx/txSubmissionButton"; +import PopupWithSigner from "next-common/components/popupWithSigner"; +import { usePopupParams } from "next-common/components/popupWithSigner/context"; +import { useContextApi } from "next-common/context/api"; +import { useRankedCollectivePallet } from "next-common/context/collectives/collectives"; +import { useCallback, useState } from "react"; +import { useReferendumVoting } from "next-common/context/fellowship/referendumVoting"; +import { useOnchainData } from "next-common/context/post"; +import { useDispatch } from "react-redux"; +import { newSuccessToast } from "next-common/store/reducers/toastSlice"; +import { fetchFellowshipReferendumVotes2Times } from "next-common/context/fellowship/referendumVoting"; +import SignerWithBalance from "next-common/components/signerPopup/signerWithBalance"; + +const successToastContent = + "Votes in storage have been cleaned up successfully."; + +function Content() { + const { onClose } = usePopupParams(); + const api = useContextApi(); + const pallet = useRankedCollectivePallet(); + const { votes, fetch } = useReferendumVoting(); + const { referendumIndex: pollIndex } = useOnchainData(); + const dispatch = useDispatch(); + const [isDisabled, setIsDisabled] = useState(false); + + const getTxFunc = useCallback(() => { + if (!api || !pallet) { + return; + } + + setIsDisabled(true); + return api?.tx?.[pallet]?.cleanupPoll(pollIndex, votes.length); + }, [api, pallet, pollIndex, votes]); + + const onInBlock = useCallback(async () => { + dispatch(newSuccessToast(successToastContent)); + await fetchFellowshipReferendumVotes2Times(fetch); + }, [dispatch, fetch]); + + return ( + <> + + + + ); +} + +export default function CleanupPopup({ onClose }) { + return ( + + + + ); +} diff --git a/packages/next-common/components/pages/fellowship/memberListView.js b/packages/next-common/components/pages/fellowship/memberListView.js index b40b6d122f..f0a4804690 100644 --- a/packages/next-common/components/pages/fellowship/memberListView.js +++ b/packages/next-common/components/pages/fellowship/memberListView.js @@ -34,13 +34,13 @@ const rankColumn = { const memberColumn = { name: "Member", - width: 140, + className: "min-w-[140px]", }; const salaryColumn = { name: "Salary", className: "text-right", - minWidth: 120, + width: 120, }; const demotionPeriodColumn = { diff --git a/packages/next-common/context/fellowship/referendumVoting.js b/packages/next-common/context/fellowship/referendumVoting.js new file mode 100644 index 0000000000..ea4ac5055d --- /dev/null +++ b/packages/next-common/context/fellowship/referendumVoting.js @@ -0,0 +1,75 @@ +import { + createContext, + useContext, + useCallback, + useEffect, + useState, +} from "react"; +import { isNil } from "lodash-es"; +import { useOnchainData } from "next-common/context/post"; +import { useContextApi } from "next-common/context/api"; +import { defaultBlockTime } from "next-common/utils/constants"; +import { sleep } from "next-common/utils"; +import { useRankedCollectivePallet } from "next-common/context/collectives/collectives"; +import getChainSettings from "next-common/utils/consts/settings"; + +const ReferendumVotingContext = createContext(); + +function useFellowshipReferendumVotes() { + const api = useContextApi(); + const [fellowshipVotes, setFellowshipVotes] = useState([]); + const [isLoading, setIsLoading] = useState(true); + const pallet = useRankedCollectivePallet(); + const { referendumIndex: pollIndex } = useOnchainData(); + + const fetch = useCallback(async () => { + if (!api || isNil(pollIndex) || !pallet) { + return; + } + + try { + const votes = await api?.query?.[pallet]?.voting?.entries(pollIndex); + setFellowshipVotes(votes); + } catch (error) { + console.error("Failed to fetch fellowship votes:", error); + setFellowshipVotes([]); + } finally { + setIsLoading(false); + } + }, [api, pollIndex, pallet]); + + useEffect(() => { + fetch(); + }, [fetch]); + + return { + votes: fellowshipVotes, + isLoading, + fetch, + }; +} + +export async function fetchFellowshipReferendumVotes2Times(fetch) { + const blockTime = + getChainSettings(process.env.NEXT_PUBLIC_CHAIN).blockTime || + defaultBlockTime; + + for (let i = 0; i < 2; i++) { + await fetch(); + await sleep(blockTime); + } +} + +export default function ReferendumVotingProvider({ children }) { + const { votes, isLoading, fetch } = useFellowshipReferendumVotes(); + + return ( + + {children} + + ); +} + +export function useReferendumVoting() { + return useContext(ReferendumVotingContext); +} diff --git a/packages/next/components/fellowship/referendum/sidebar/index.js b/packages/next/components/fellowship/referendum/sidebar/index.js index 4ffba9b0e2..7ef65968de 100644 --- a/packages/next/components/fellowship/referendum/sidebar/index.js +++ b/packages/next/components/fellowship/referendum/sidebar/index.js @@ -19,6 +19,7 @@ import Tooltip from "next-common/components/tooltip"; import dynamic from "next/dynamic"; import AllSpendsRequest from "./request/allSpendsRequest"; import useRankedCollectiveMinRank from "next-common/hooks/collectives/useRankedCollectiveMinRank"; +import FellowshipReferendumCleanupPoll from "next-common/components/fellowship/referenda/cleanupPoll"; const MyCollectiveVote = dynamic( () => import("next-common/components/collectives/referenda/myCollectiveVote"), @@ -85,6 +86,7 @@ export default function FellowshipReferendumSideBar() { /> )} +