From 21f9a99071e5cc8798c2cf1088e3f8c9fc339ec6 Mon Sep 17 00:00:00 2001 From: jeremy-babylonchain Date: Fri, 13 Dec 2024 20:14:23 +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 | 23 ++- .../Delegations/components/DelegationCell.tsx | 11 ++ .../components/DelegationStatus.tsx | 27 +++ .../Delegations/components/OverflowBadge.tsx | 8 + src/app/components/Modals/UnbondModal.tsx | 2 - .../components/DelegationModal.tsx | 1 - 8 files changed, 244 insertions(+), 124 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 a59283cf..1d19f0f4 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 { type SigningStep, useTransactionService, @@ -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 } = useFinalityProviderState(); // get the moniker of the finality provider - useEffect(() => { - const timerId = setInterval(() => { - setCurrentTime(Date.now()); - }, 60000); // set the refresh interval to 60 seconds + const { getFinalityProviderMoniker } = useFinalityProviderState(); + 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 7d68115d..6396852c 100644 --- a/src/app/components/Delegations/Delegations.tsx +++ b/src/app/components/Delegations/Delegations.tsx @@ -20,12 +20,14 @@ import { getIntermediateDelegationsLocalStorageKey } from "@/utils/local_storage import { toLocalStorageIntermediateDelegation } from "@/utils/local_storage/toLocalStorageIntermediateDelegation"; import { Phase2HereModal } from "../Modals/Phase2Here"; +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(); @@ -296,6 +298,7 @@ export const Delegations = ({}) => { onWithdraw={() => handleModal(stakingTxHashHex, MODE_WITHDRAW) } + onUnbond={() => handleModal(stakingTxHashHex, MODE_UNBOND)} intermediateState={intermediateDelegation?.state} /> ); @@ -303,6 +306,10 @@ export const Delegations = ({}) => {
+ setShowPhase2HereModal(false)} + /> {modalMode && txID && selectedDelegation && ( { processing={awaitingWalletResponse} /> )} - setShowPhase2HereModal(false)} - /> + {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; } diff --git a/src/components/delegations/DelegationList/components/DelegationModal.tsx b/src/components/delegations/DelegationList/components/DelegationModal.tsx index d280a45e..90178d34 100644 --- a/src/components/delegations/DelegationList/components/DelegationModal.tsx +++ b/src/components/delegations/DelegationList/components/DelegationModal.tsx @@ -37,7 +37,6 @@ export function DelegationModal({ />