From f52b39df4df0a3aa74a5d0dcbb77d5fe3de491b8 Mon Sep 17 00:00:00 2001 From: jeremy-babylonchain Date: Fri, 13 Dec 2024 20:09:00 +0800 Subject: [PATCH] add phase 1 unbond button and hook up modal --- src/app/components/Delegations/Delegation.tsx | 178 ++++++------------ .../Delegations/DelegationActions.tsx | 118 ++++++++++++ .../components/Delegations/Delegations.tsx | 16 +- .../Delegations/components/DelegationCell.tsx | 11 ++ .../components/DelegationStatus.tsx | 27 +++ .../Delegations/components/OverflowBadge.tsx | 8 + src/app/components/Modals/UnbondModal.tsx | 2 - 7 files changed, 241 insertions(+), 119 deletions(-) create mode 100644 src/app/components/Delegations/DelegationActions.tsx create mode 100644 src/app/components/Delegations/components/DelegationCell.tsx create mode 100644 src/app/components/Delegations/components/DelegationStatus.tsx create mode 100644 src/app/components/Delegations/components/OverflowBadge.tsx diff --git a/src/app/components/Delegations/Delegation.tsx b/src/app/components/Delegations/Delegation.tsx index 14f7e771..12e1f40d 100644 --- a/src/app/components/Delegations/Delegation.tsx +++ b/src/app/components/Delegations/Delegation.tsx @@ -1,9 +1,7 @@ import { useEffect, useState } from "react"; -import { AiOutlineInfoCircle } from "react-icons/ai"; import { FaBitcoin } from "react-icons/fa"; -import { IoIosWarning } from "react-icons/io"; -import { Tooltip } from "react-tooltip"; +import { DelegationActions } from "@/app/components/Delegations/DelegationActions"; import { useFinalityProviderService } from "@/app/hooks/services/useFinalityProviderService"; import { type SigningStep, @@ -21,9 +19,14 @@ import { maxDecimals } from "@/utils/maxDecimals"; import { durationTillNow } from "@/utils/time"; import { trim } from "@/utils/trim"; +import { DelegationCell } from "./components/DelegationCell"; +import { DelegationStatus } from "./components/DelegationStatus"; +import { OverflowBadge } from "./components/OverflowBadge"; + interface DelegationProps { delegation: DelegationInterface; onWithdraw: (id: string) => void; + onUnbond: (id: string) => void; // This attribute is set when an action has been taken by the user // that should change the status but the back-end // has not had time to reflect this change yet @@ -33,6 +36,7 @@ interface DelegationProps { export const Delegation: React.FC = ({ delegation, onWithdraw, + onUnbond, intermediateState, }) => { const { @@ -42,18 +46,18 @@ export const Delegation: React.FC = ({ stakingValueSat, isOverflow, finalityProviderPkHex, + isEligibleForTransition, } = delegation; const { startTimestamp } = stakingTx; const [currentTime, setCurrentTime] = useState(Date.now()); const { isApiNormal, isGeoBlocked } = useHealthCheck(); const { transitionPhase1Delegation } = useTransactionService(); - const { getFinalityProviderMoniker } = useFinalityProviderService(); // get the moniker of the finality provider - useEffect(() => { - const timerId = setInterval(() => { - setCurrentTime(Date.now()); - }, 60000); // set the refresh interval to 60 seconds + const { getFinalityProviderMoniker } = useFinalityProviderService(); + const { coinName, mempoolApiUrl } = getNetworkConfig(); + useEffect(() => { + const timerId = setInterval(() => setCurrentTime(Date.now()), 60000); // Update every minute return () => clearInterval(timerId); }, []); @@ -77,110 +81,48 @@ export const Delegation: React.FC = ({ // TODO: Close the transaction signing modal and update the UI }; - const generateActionButton = () => { - if ( - state === DelegationState.ACTIVE || - delegation.isEligibleForTransition - ) { - return ( -
- - -
- ); - } else if (state === DelegationState.UNBONDED) { - return ( -
- -
- ); - } else { - return null; - } - }; - const isActive = intermediateState === DelegationState.ACTIVE || state === DelegationState.ACTIVE; - const renderState = () => { + const renderState = () => // overflow should be shown only on active state - if (isOverflow && isActive) { - return getState(DelegationState.OVERFLOW); - } else { - return getState(intermediateState || state); - } - }; + isOverflow && isActive + ? getState(DelegationState.OVERFLOW) + : getState(intermediateState || state); - const renderStateTooltip = () => { + const renderStateTooltip = () => // overflow should be shown only on active state - if (isOverflow && isActive) { - return getStateTooltip(DelegationState.OVERFLOW); - } else { - return getStateTooltip(intermediateState || state); - } - }; - - const { coinName, mempoolApiUrl } = getNetworkConfig(); - - const renderActionRequired = () => { - return

Action Required

; - }; + isOverflow && isActive + ? getStateTooltip(DelegationState.OVERFLOW) + : getStateTooltip(intermediateState || state); return ( -
- {isOverflow && ( -
- -

overflow

-
- )} -
-
+
+ {isOverflow && } +
+ {durationTillNow(startTimestamp, currentTime)} -
-
+ + + {getFinalityProviderMoniker(finalityProviderPkHex)} -
-
+ + +

{maxDecimals(satoshiToBtc(stakingValueSat), 8)} {coinName}

-
- + {/* we need to center the text without the tooltip add its size 12px and gap 4px, 16/2 = 8px */} -
-
-

{renderState()}

- - - - -
-
-
{generateActionButton()}
+ + + + + + +
); diff --git a/src/app/components/Delegations/DelegationActions.tsx b/src/app/components/Delegations/DelegationActions.tsx new file mode 100644 index 00000000..e55b125c --- /dev/null +++ b/src/app/components/Delegations/DelegationActions.tsx @@ -0,0 +1,118 @@ +import { Button, Popover } from "@babylonlabs-io/bbn-core-ui"; +import { useState } from "react"; +import { IoMdMore } from "react-icons/io"; +import { Tooltip } from "react-tooltip"; + +import { DelegationState } from "@/app/types/delegations"; + +interface DelegationActionsProps { + state: string; + intermediateState?: string; + isEligibleForTransition: boolean; + stakingTxHashHex: string; + onTransition: () => Promise; + onUnbond: (id: string) => void; + onWithdraw: (id: string) => void; +} + +export const DelegationActions: React.FC = ({ + state, + intermediateState, + isEligibleForTransition, + stakingTxHashHex, + onTransition, + onUnbond, + onWithdraw, +}) => { + const [isPopoverOpen, setIsPopoverOpen] = useState(false); + const [anchorEl, setAnchorEl] = useState(null); + + // Unbonded + if (state === DelegationState.UNBONDED) { + return ( +
+ +
+ ); + } + + // Active, eligible for transition + if ( + state === DelegationState.ACTIVE || + isEligibleForTransition || + state === DelegationState.WITHDRAWN + ) { + return ( +
+
+ + +
+ + + + setIsPopoverOpen(false)} + > +
+ +
+
+
+ ); + } + + return null; +}; diff --git a/src/app/components/Delegations/Delegations.tsx b/src/app/components/Delegations/Delegations.tsx index 47a74099..aba45378 100644 --- a/src/app/components/Delegations/Delegations.tsx +++ b/src/app/components/Delegations/Delegations.tsx @@ -19,11 +19,14 @@ import { ErrorState } from "@/app/types/errors"; import { getIntermediateDelegationsLocalStorageKey } from "@/utils/local_storage/getIntermediateDelegationsLocalStorageKey"; import { toLocalStorageIntermediateDelegation } from "@/utils/local_storage/toLocalStorageIntermediateDelegation"; +import { UnbondModal } from "../Modals/UnbondModal"; + import { Delegation } from "./Delegation"; const MODE_TRANSITION = "transition"; const MODE_WITHDRAW = "withdraw"; -type MODE = typeof MODE_TRANSITION | typeof MODE_WITHDRAW; +const MODE_UNBOND = "unbond"; +type MODE = typeof MODE_TRANSITION | typeof MODE_WITHDRAW | typeof MODE_UNBOND; export const Delegations = ({}) => { const { publicKeyNoCoord, connected, network } = useBTCWallet(); @@ -293,6 +296,7 @@ export const Delegations = ({}) => { onWithdraw={() => handleModal(stakingTxHashHex, MODE_WITHDRAW) } + onUnbond={() => handleModal(stakingTxHashHex, MODE_UNBOND)} intermediateState={intermediateDelegation?.state} /> ); @@ -310,6 +314,16 @@ export const Delegations = ({}) => { processing={awaitingWalletResponse} /> )} + {modalMode === MODE_UNBOND && ( + setModalOpen(false)} + onSubmit={() => { + handleUnbond(txID); + }} + processing={awaitingWalletResponse} + /> + )} ); }; diff --git a/src/app/components/Delegations/components/DelegationCell.tsx b/src/app/components/Delegations/components/DelegationCell.tsx new file mode 100644 index 00000000..d02158ee --- /dev/null +++ b/src/app/components/Delegations/components/DelegationCell.tsx @@ -0,0 +1,11 @@ +interface DelegationCellProps { + children: React.ReactNode; + order: string; + className?: string; +} + +export const DelegationCell: React.FC = ({ + children, + order, + className = "", +}) =>
{children}
; diff --git a/src/app/components/Delegations/components/DelegationStatus.tsx b/src/app/components/Delegations/components/DelegationStatus.tsx new file mode 100644 index 00000000..1a55f6d4 --- /dev/null +++ b/src/app/components/Delegations/components/DelegationStatus.tsx @@ -0,0 +1,27 @@ +import { AiOutlineInfoCircle } from "react-icons/ai"; +import { Tooltip } from "react-tooltip"; + +interface DelegationStatusProps { + state: string; + tooltip: string; + stakingTxHashHex: string; +} + +export const DelegationStatus: React.FC = ({ + state, + tooltip, + stakingTxHashHex, +}) => ( +
+

{state}

+ + + + +
+); diff --git a/src/app/components/Delegations/components/OverflowBadge.tsx b/src/app/components/Delegations/components/OverflowBadge.tsx new file mode 100644 index 00000000..d49bbda1 --- /dev/null +++ b/src/app/components/Delegations/components/OverflowBadge.tsx @@ -0,0 +1,8 @@ +import { IoIosWarning } from "react-icons/io"; + +export const OverflowBadge: React.FC = () => ( +
+ +

overflow

+
+); diff --git a/src/app/components/Modals/UnbondModal.tsx b/src/app/components/Modals/UnbondModal.tsx index 307664f1..8d4aec3e 100644 --- a/src/app/components/Modals/UnbondModal.tsx +++ b/src/app/components/Modals/UnbondModal.tsx @@ -1,6 +1,5 @@ import { Text } from "@babylonlabs-io/bbn-core-ui"; -import { DelegationV2 } from "@/app/types/delegationsV2"; import { getNetworkConfig } from "@/config/network.config"; import { ConfirmationModal } from "./ConfirmationModal"; @@ -8,7 +7,6 @@ import { ConfirmationModal } from "./ConfirmationModal"; interface UnbondModalProps { processing: boolean; open: boolean; - delegation: DelegationV2 | null; onClose: () => void; onSubmit: () => void; }