diff --git a/src/components/v5/common/ActionSidebar/partials/Motions/MotionStep/FinalizeStep/FinalizeStep.tsx b/src/components/v5/common/ActionSidebar/partials/Motions/MotionStep/FinalizeStep/FinalizeStep.tsx index 1454b5be79..4de9d62d01 100644 --- a/src/components/v5/common/ActionSidebar/partials/Motions/MotionStep/FinalizeStep/FinalizeStep.tsx +++ b/src/components/v5/common/ActionSidebar/partials/Motions/MotionStep/FinalizeStep/FinalizeStep.tsx @@ -58,6 +58,7 @@ const FinalizeStep: FC = ({ handleClaimSuccess, claimPayload, canClaimStakes, + hasUserStake, } = useClaimConfig({ action, motionData }); const { type: actionType } = action; @@ -73,12 +74,14 @@ const FinalizeStep: FC = ({ const { setActionsTableTriggers } = useColonyTriggersContext(); const previousIsMotionFinalized = usePrevious(isMotionFinalized); + const isMotionFailed = motionData.motionStateHistory.hasFailed; const isMotionAgreement = - actionType === ColonyActionType.CreateDecisionMotion; - - const isMotionClaimable = - isMotionFinalized || - isMotionFailedNotFinalizable || + action.type === ColonyActionType.CreateDecisionMotion; + const isMotionClaimable = isMotionFinalized && !isClaimed; + const isAgreementClaimable = + ((isMotionFinalized || isMotionFailedNotFinalizable) && + !isMotionFailed && + !isMotionFailedNotFinalizable) || (isMotionAgreement && !isClaimed); const handleSuccess = () => { @@ -119,9 +122,17 @@ const FinalizeStep: FC = ({ const statusId = (() => { if (isMotionAgreement) { - return isClaimed - ? 'motion.finalizeStep.finalizeAgreement' - : 'motion.finalizeStep.claimable.finalizeAgreement'; + const userNotStakeOrClaimedText = isMotionFailed + ? 'motion.finalizeStep.finalizeAgreement.failed' + : 'motion.finalizeStep.finalizeAgreement.supported'; + + const userStakeUnclaimedText = isMotionFailed + ? 'motion.finalizeStep.claimable.finalizeAgreement.failed' + : 'motion.finalizeStep.claimable.finalizeAgreement.passed'; + + return isClaimed || !hasUserStake + ? userNotStakeOrClaimedText + : userStakeUnclaimedText; } return isMotionFailedNotFinalizable @@ -132,7 +143,26 @@ const FinalizeStep: FC = ({ const showFinalizeButton = !isMotionFailedNotFinalizable && !isMotionFinalized && !isMotionAgreement; - const showClaimButton = isMotionClaimable && canClaimStakes && !isClaimed; + const showClaimButton = + (isMotionClaimable && canClaimStakes && !isClaimed && hasUserStake) || + (isMotionAgreement && isAgreementClaimable && hasUserStake && !isClaimed); + const canBeExecuted = + !isPolling && + !isMotionFailedNotFinalizable && + !isMotionFinalized && + !isMotionAgreement; + + const supportedStatusText = canBeExecuted + ? 'motion.finalizeStep.passedAction' + : 'motion.finalizeStep.completedStatusText'; + + const finalizeStatusText = isMotionFailed + ? 'motion.finalizeStep.opposedAction' + : supportedStatusText; + + const statusText = isMotionFailedNotFinalizable + ? 'motion.finalizeStep.failed.statusText' + : finalizeStatusText; return ( = ({ iconAlignment="top" iconSize={16} > - {formatText({ id: statusId })} + {actionType === ColonyActionType.CreateDecisionMotion + ? formatText({ id: statusId }) + : formatText({ + id: statusText, + })} } content={ diff --git a/src/components/v5/common/ActionSidebar/partials/Motions/MotionStep/FinalizeStep/hooks.tsx b/src/components/v5/common/ActionSidebar/partials/Motions/MotionStep/FinalizeStep/hooks.tsx index 13036a1ad3..0f78805c17 100644 --- a/src/components/v5/common/ActionSidebar/partials/Motions/MotionStep/FinalizeStep/hooks.tsx +++ b/src/components/v5/common/ActionSidebar/partials/Motions/MotionStep/FinalizeStep/hooks.tsx @@ -1,4 +1,5 @@ import { Extension, Id } from '@colony/colony-js'; +import { format } from 'date-fns'; import { BigNumber } from 'ethers'; import React, { useEffect, useMemo, useState } from 'react'; @@ -123,6 +124,7 @@ export const useClaimConfig = ({ motionStateHistory.hasFailedNotFinalizable; const userStake = usersStakes.find(({ address }) => address === userAddress); + const stakerReward = stakerRewards.find( ({ address }) => address === userAddress, ); @@ -133,20 +135,21 @@ export const useClaimConfig = ({ } }, [stakerReward?.isClaimed, isClaimed]); + const userReward = voterRewards?.items.find( + (voterReward) => voterReward?.userAddress === userAddress, + ); + const userVoteRewardAmount = useMemo(() => { if (!userAddress || !voterRewards?.items) { return 0; } - const userReward = voterRewards.items.find( - (voterReward) => voterReward?.userAddress === userAddress, - ); if (!userReward) { return 0; } return userReward.amount; - }, [userAddress, voterRewards]); + }, [userAddress, voterRewards, userReward]); const userTotalStake = useMemo( () => @@ -177,7 +180,9 @@ export const useClaimConfig = ({ // Else, return full widget const buttonTextId = isClaimed ? 'button.claimed' : 'button.claim'; const remainingStakesNumber = remainingStakes.length; - const canClaimStakes = userTotalStake ? !userTotalStake.isZero() : false; + const canClaimStakes = userTotalStake + ? !userTotalStake.isZero() + : !!userReward; const handleClaimSuccess = () => { setIsClaimed(true); pollLockedTokenBalance(); @@ -263,6 +268,33 @@ export const useClaimConfig = ({ ), }, + { + key: WinningsItems.Completed, + label: formatText({ id: 'motion.finalizeStep.completed' }), + value: ( +
+ +
+ ), + }, ]; }; @@ -276,5 +308,6 @@ export const useClaimConfig = ({ handleClaimSuccess, claimPayload, canClaimStakes, + hasUserStake: !!userStake, }; }; diff --git a/src/components/v5/common/ActionSidebar/partials/Motions/MotionStep/FinalizeStep/types.ts b/src/components/v5/common/ActionSidebar/partials/Motions/MotionStep/FinalizeStep/types.ts index a7ad13339d..24247f0cc1 100644 --- a/src/components/v5/common/ActionSidebar/partials/Motions/MotionStep/FinalizeStep/types.ts +++ b/src/components/v5/common/ActionSidebar/partials/Motions/MotionStep/FinalizeStep/types.ts @@ -13,4 +13,5 @@ export enum WinningsItems { Staked = 'staked', Winnings = 'winnings', Total = 'total', + Completed = 'completed', } diff --git a/src/components/v5/common/ActionSidebar/partials/Motions/steps/FinalizeStep/types.ts b/src/components/v5/common/ActionSidebar/partials/Motions/steps/FinalizeStep/types.ts new file mode 100644 index 0000000000..e7606beb0b --- /dev/null +++ b/src/components/v5/common/ActionSidebar/partials/Motions/steps/FinalizeStep/types.ts @@ -0,0 +1,22 @@ +import { type ColonyActionFragment } from '~gql'; +import { type MotionState } from '~utils/colonyMotions.ts'; +import { type RefetchAction } from '~v5/common/ActionSidebar/hooks/useGetColonyAction.ts'; + +export interface FinalizeStepProps { + startPollingAction: (pollingInterval: number) => void; + stopPollingAction: () => void; + refetchAction: RefetchAction; + actionData: ColonyActionFragment; + motionState?: MotionState; +} + +export enum FinalizeStepSections { + Finalize = 'finalizeStep', +} + +export enum WinningsItems { + Staked = 'staked', + Winnings = 'winnings', + Total = 'total', + Completed = 'completed', +} diff --git a/src/i18n/en.json b/src/i18n/en.json index 6116e04bb8..e76a25d5e5 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -1465,15 +1465,23 @@ "motion.staking.accordion.title.hide": "Hide staking information", "motion.finalize.label": "Finalize", "motion.finalizeStep.statusText": "Finalize to execute the agreed transactions and return stakes.", - "motion.finalizeStep.finalizeAgreement": "Agreement process is complete and stakes have been returned.", - "motion.finalizeStep.claimable.finalizeAgreement": "Agreement process is complete, finalize to return stakes.", + "motion.finalizeStep.finalizeAgreement.failed": "Agreement has failed and now is complete.", + "motion.finalizeStep.finalizeAgreement.supported": "Agreement has passed and now is complete.", + "motion.finalizeStep.claimable.finalizeAgreement.failed": "Agreement has failed, stakes can be claimed.", + "motion.finalizeStep.claimable.finalizeAgreement.passed": "Agreement has passed, stakes can be claimed.", + "motion.finalizeStep.completedStatusText": "Action has passed and is now complete.", "motion.finalizeStep.claimable.statusText": "Action has passed and has been executed.", - "motion.finalizeStep.title": "Your overview", + "motion.finalizeStep.title": "Overview", "motion.finalizeStep.failedNotAchieving.statusText": "Action failed due to not achieving required stake.", + "motion.finalizeStep.complete.statusText": "Action has passed and is now complete.", "motion.finalizeStep.failed.statusText": "Action failed and cannot be executed.", - "motion.finalizeStep.staked": "Staked", - "motion.finalizeStep.winnings": "Reward", + "motion.finalizeStep.passedAction": "Action has passed and can be executed.", + "motion.finalizeStep.opposedAction": "Action was opposed and cannot be executed.", + "motion.finalizeStep.staked": "Your stake", + "motion.finalizeStep.winnings": "Your reward", "motion.finalizeStep.total": "Total", + "motion.finalizeStep.completed": "Completed", + "motion.finalizeStep.completedAt": "{date} at {hour}", "motion.finalizeStep.submit": "Finalize", "motion.finalizeStep.returnStakes": "Return stakes", "motion.finalizeStep.transactions.remaining": "{transactions} transactions remaining", diff --git a/src/redux/sagas/motions/claimMotionRewards.ts b/src/redux/sagas/motions/claimMotionRewards.ts index cce97fbdf6..0a7d66bcb1 100644 --- a/src/redux/sagas/motions/claimMotionRewards.ts +++ b/src/redux/sagas/motions/claimMotionRewards.ts @@ -79,10 +79,6 @@ function* claimMotionRewards({ const hasYayStake = !BigNumber.from(yayStake).isZero(); const hasNayStake = !BigNumber.from(nayStake).isZero(); - if (!hasYayStake && !hasNayStake) { - throw new Error('A motion with claims needs to be provided'); - } - const YAY_ID = 'yayClaim'; const NAY_ID = 'nayClaim';