-
Notifications
You must be signed in to change notification settings - Fork 6
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
* Init ProposeCuratorPopup, #4782 * Add ProposeCuratorPopup balance & fee fileds, #4782 * Add getCheckedFee, #4782 * Add proposeCurator params, #4782 * proposeCurator params, #4782 * modify proposeCurator params, #4782 * Fix lint error, #4782 * Proposed button null guard, #4782 * modify state, #4782 * add disabled judgment, #4782 * Fix lint error, #4782 * Modify parentCurator judgment, #4782 * Add useFeeAmount, #4782 * optimize code, #4782 * optimize code, #4782 * add useSubBountyOnChainData, #4782 * rename useSubParentBountyData, #4782 * optimize code, #4782 * fix useSubChildBountyIsAdded, #4782 * fix lint error, #4782
- Loading branch information
Showing
4 changed files
with
312 additions
and
0 deletions.
There are no files selected for viewing
91 changes: 91 additions & 0 deletions
91
packages/next-common/components/treasury/childBounty/proposeCurator/index.jsx
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,91 @@ | ||
import React, { useEffect, useState } from "react"; | ||
import useProposeCuratorPopup from "./useProposeCurator"; | ||
import PrimaryButton from "next-common/lib/button/primary"; | ||
import { useOnchainData } from "next-common/context/post"; | ||
import Tooltip from "next-common/components/tooltip"; | ||
import useRealAddress from "next-common/utils/hooks/useRealAddress"; | ||
import useSubStorage from "next-common/hooks/common/useSubStorage"; | ||
|
||
function useSubParentBountyData(bountyIndex) { | ||
const { result, loading } = useSubStorage("bounties", "bounties", [ | ||
bountyIndex, | ||
]); | ||
const data = result?.toJSON(); | ||
|
||
return { | ||
status: data?.status, | ||
loading, | ||
}; | ||
} | ||
|
||
function useSubChildBountyIsAdded(parentBountyId, index) { | ||
const { loading, result: onChainStorage } = useSubStorage( | ||
"childBounties", | ||
"childBounties", | ||
[parentBountyId, index], | ||
); | ||
|
||
if (loading || !onChainStorage?.isSome) { | ||
return false; | ||
} | ||
const { status } = onChainStorage.toJSON(); | ||
if (!status || !("added" in status)) { | ||
return false; | ||
} | ||
return true; | ||
} | ||
|
||
function isParentBountyCurator(status = {}, address) { | ||
for (const item of Object.values(status)) { | ||
if (item?.curator && item.curator === address) { | ||
return true; | ||
} | ||
} | ||
return false; | ||
} | ||
|
||
export default function ProposeCurator() { | ||
const address = useRealAddress(); | ||
const [isDisabled, setIsDisabled] = useState(true); | ||
const [disabledTooltip, setDisabledTooltip] = useState(""); | ||
const { showPopupFn, component: ProposeCuratorPopup } = | ||
useProposeCuratorPopup(); | ||
const { parentBountyId, index } = useOnchainData(); | ||
const { status, loading } = useSubParentBountyData(parentBountyId); | ||
const isAddedState = useSubChildBountyIsAdded(parentBountyId, index); | ||
|
||
// The dispatch origin for this call must be curator of parent bounty. | ||
useEffect(() => { | ||
if (loading) { | ||
return; | ||
} | ||
|
||
const isParentCurator = isParentBountyCurator(status, address); | ||
setIsDisabled(!isParentCurator); | ||
|
||
if (!isParentCurator) { | ||
const disabledTooltipContent = | ||
"Only parent bounty curator can propose a curator"; | ||
setDisabledTooltip(disabledTooltipContent); | ||
} | ||
}, [loading, address, status]); | ||
|
||
if (!address || !isAddedState) { | ||
return null; | ||
} | ||
|
||
return ( | ||
<> | ||
<Tooltip content={disabledTooltip}> | ||
<PrimaryButton | ||
className="w-full" | ||
onClick={() => showPopupFn()} | ||
disabled={isDisabled} | ||
> | ||
Propose Curator | ||
</PrimaryButton> | ||
</Tooltip> | ||
{ProposeCuratorPopup} | ||
</> | ||
); | ||
} |
113 changes: 113 additions & 0 deletions
113
packages/next-common/components/treasury/childBounty/proposeCurator/useFeeAmount.jsx
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,113 @@ | ||
import { formatBalance } from "next-common/components/assets/assetsList"; | ||
import BalanceDisplay from "next-common/components/assets/balanceDisplay"; | ||
import Loading from "next-common/components/loading"; | ||
import PopupLabel from "next-common/components/popup/label"; | ||
import Input from "next-common/components/input"; | ||
import { useCallback, useState } from "react"; | ||
import BigNumber from "bignumber.js"; | ||
|
||
function checkFeeAmount({ feeAmount, decimals, balance }) { | ||
if (!feeAmount) { | ||
throw new Error("Please fill the fee"); | ||
} | ||
|
||
const amount = new BigNumber(feeAmount).times(Math.pow(10, decimals)); | ||
if (amount.isNaN() || amount.lte(0) || !amount.isInteger()) { | ||
throw new Error("Invalid fee"); | ||
} | ||
if (balance && amount.gte(balance)) { | ||
throw new Error("Fee should be less than child bounty value"); | ||
} | ||
|
||
return amount.toFixed(); | ||
} | ||
|
||
function MaxBalance({ value, isLoading, decimals, symbol }) { | ||
return ( | ||
<div className="flex gap-[8px] items-center mb-[8px]"> | ||
<span className="text12Medium text-textTertiary leading-none">Max</span> | ||
{isLoading ? ( | ||
<Loading size={12} /> | ||
) : ( | ||
<span> | ||
<BalanceDisplay balance={formatBalance(value, decimals)} /> | ||
<span className="text-textPrimary ml-1">{symbol}</span> | ||
</span> | ||
)} | ||
</div> | ||
); | ||
} | ||
|
||
function FeeAmount({ | ||
balance, | ||
decimals, | ||
symbol, | ||
isLoading, | ||
address, | ||
feeAmount, | ||
setFeeAmount, | ||
}) { | ||
const handleFeeChange = useCallback( | ||
(e) => { | ||
setFeeAmount(e.target.value.replace("。", ".")); | ||
}, | ||
[setFeeAmount], | ||
); | ||
|
||
return ( | ||
<div> | ||
<PopupLabel | ||
text="Fee" | ||
status={ | ||
!!address && ( | ||
<MaxBalance | ||
value={balance} | ||
isLoading={isLoading} | ||
decimals={decimals} | ||
symbol={symbol} | ||
/> | ||
) | ||
} | ||
/> | ||
<Input | ||
type="text" | ||
placeholder="0.00" | ||
value={feeAmount} | ||
onChange={handleFeeChange} | ||
symbol={symbol} | ||
/> | ||
</div> | ||
); | ||
} | ||
|
||
export default function useFeeAmount(props = {}) { | ||
const { balance, decimals, symbol, address, isLoading } = props; | ||
|
||
const [feeAmount, setFeeAmount] = useState(""); | ||
|
||
const component = ( | ||
<FeeAmount | ||
balance={balance} | ||
decimals={decimals} | ||
symbol={symbol} | ||
isLoading={isLoading} | ||
address={address} | ||
feeAmount={feeAmount} | ||
setFeeAmount={setFeeAmount} | ||
/> | ||
); | ||
|
||
const getCheckedValue = useCallback(() => { | ||
return checkFeeAmount({ | ||
feeAmount, | ||
decimals, | ||
balance, | ||
}); | ||
}, [feeAmount, decimals, balance]); | ||
|
||
return { | ||
value: feeAmount, | ||
component, | ||
getCheckedValue, | ||
}; | ||
} |
106 changes: 106 additions & 0 deletions
106
packages/next-common/components/treasury/childBounty/proposeCurator/useProposeCurator.jsx
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,106 @@ | ||
import TxSubmissionButton from "next-common/components/common/tx/txSubmissionButton"; | ||
import Signer from "next-common/components/popup/fields/signerField"; | ||
import PopupWithSigner from "next-common/components/popupWithSigner"; | ||
import { usePopupParams } from "next-common/components/popupWithSigner/context"; | ||
import useAddressComboField from "next-common/components/preImages/createPreimagePopup/fields/useAddressComboField"; | ||
import { useContextApi } from "next-common/context/api"; | ||
import { newErrorToast } from "next-common/store/reducers/toastSlice"; | ||
import useRealAddress from "next-common/utils/hooks/useRealAddress"; | ||
import { useCallback, useState } from "react"; | ||
import { useDispatch } from "react-redux"; | ||
import { useSubBalanceInfo } from "next-common/hooks/balance/useSubBalanceInfo"; | ||
import { useChainSettings } from "next-common/context/chain"; | ||
import { useOnchainData } from "next-common/context/post"; | ||
import useFeeAmount from "./useFeeAmount"; | ||
import useSubAddressBalance from "next-common/utils/hooks/useSubAddressBalance"; | ||
|
||
function PopupContent() { | ||
const { onClose } = usePopupParams(); | ||
const { decimals, symbol } = useChainSettings(); | ||
const address = useRealAddress(); | ||
const { value: signerBalance, loading: signerBalanceLoading } = | ||
useSubBalanceInfo(address); | ||
const api = useContextApi(); | ||
const dispatch = useDispatch(); | ||
|
||
const { | ||
parentBountyId, | ||
index: childBountyId, | ||
address: childBountyAddress, | ||
} = useOnchainData(); | ||
const { balance: metadataBalance, isLoading: metadataBalanceLoading } = | ||
useSubAddressBalance(childBountyAddress); | ||
|
||
const { getCheckedValue: getCheckedFee, component: feeField } = useFeeAmount({ | ||
balance: metadataBalance, | ||
decimals, | ||
symbol, | ||
address, | ||
isLoading: metadataBalanceLoading, | ||
}); | ||
|
||
const { value: curator, component: curatorSelect } = useAddressComboField({ | ||
title: "Curator", | ||
}); | ||
|
||
const getTxFunc = useCallback(() => { | ||
if (!curator) { | ||
dispatch(newErrorToast("Please enter the recipient address")); | ||
return; | ||
} | ||
|
||
let fee; | ||
try { | ||
fee = getCheckedFee(); | ||
} catch (e) { | ||
dispatch(newErrorToast(e.message)); | ||
return; | ||
} | ||
|
||
return api.tx.childBounties?.proposeCurator( | ||
parentBountyId, | ||
childBountyId, | ||
curator, | ||
fee, | ||
); | ||
}, [curator, getCheckedFee, parentBountyId, childBountyId, api, dispatch]); | ||
|
||
return ( | ||
<> | ||
<Signer | ||
balanceName="Available" | ||
signerBalance={signerBalance?.balance} | ||
isSignerBalanceLoading={signerBalanceLoading} | ||
title="Origin" | ||
/> | ||
{curatorSelect} | ||
{feeField} | ||
<div className="flex justify-end"> | ||
<TxSubmissionButton | ||
title="Confirm" | ||
getTxFunc={getTxFunc} | ||
onClose={onClose} | ||
/> | ||
</div> | ||
</> | ||
); | ||
} | ||
|
||
function ProposeCuratorPopup(props) { | ||
return ( | ||
<PopupWithSigner title="Propose Curator" className="!w-[640px]" {...props}> | ||
<PopupContent /> | ||
</PopupWithSigner> | ||
); | ||
} | ||
|
||
export default function useProposeCuratorPopup() { | ||
const [isOpen, setIsOpen] = useState(false); | ||
|
||
return { | ||
showPopupFn: () => setIsOpen(true), | ||
component: isOpen ? ( | ||
<ProposeCuratorPopup onClose={() => setIsOpen(false)} /> | ||
) : null, | ||
}; | ||
} |
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