Skip to content

Commit

Permalink
feat(tangle-dapp): Add Unstake action to LsMyPoolsTable
Browse files Browse the repository at this point in the history
  • Loading branch information
yurixander committed Oct 3, 2024
1 parent 131818b commit 8d50504
Show file tree
Hide file tree
Showing 6 changed files with 152 additions and 12 deletions.
19 changes: 18 additions & 1 deletion apps/tangle-dapp/app/liquid-staking/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,12 @@ const LiquidStakingPage: FC = () => {
value ? SearchParamAction.STAKE : SearchParamAction.UNSTAKE,
});

const { lsNetworkId } = useLsStore();
const {
lsNetworkId,
setIsStaking: setIsStakingInStore,
isStaking: isStakingInStore,
} = useLsStore();

const { network } = useNetworkStore();
const { switchNetwork } = useNetworkSwitcher();

Expand All @@ -57,6 +62,18 @@ const LiquidStakingPage: FC = () => {
}
}, [lsTangleNetwork, network.id, lsNetworkId, switchNetwork]);

// Sync the Zustand store state of whether liquid staking or unstaking with
// the URL param state.
useEffect(() => {
setIsStakingInStore(isStaking);
}, [isStaking, setIsStakingInStore]);

// Sync the URL param state of whether liquid staking or unstaking with
// the Zustand store state.
useEffect(() => {
setIsStaking(isStakingInStore);
}, [isStakingInStore, setIsStaking]);

return (
<div className="flex items-stretch flex-col gap-10">
<div className="p-6 space-y-0 rounded-2xl flex flex-row items-center justify-between w-full overflow-x-auto bg-liquid_staking_banner dark:bg-liquid_staking_banner_dark">
Expand Down
46 changes: 46 additions & 0 deletions apps/tangle-dapp/components/BlueIconButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { IconBase } from '@webb-tools/icons/types';
import {
IconButton,
Tooltip,
TooltipBody,
TooltipTrigger,
} from '@webb-tools/webb-ui-components';
import { FC, JSX } from 'react';
import { twMerge } from 'tailwind-merge';

export type BlueIconButtonProps = {
onClick: () => unknown;
tooltip: string;
Icon: (props: IconBase) => JSX.Element;
isDisabled?: boolean;
};

const BlueIconButton: FC<BlueIconButtonProps> = ({
onClick,
tooltip,
Icon,
isDisabled = false,
}) => {
return (
<Tooltip>
<TooltipTrigger asChild>
<IconButton
onClick={onClick}
className={twMerge(
'bg-blue-10 hover:bg-blue-10 dark:bg-blue-120 dark:hover:bg-blue-110',
isDisabled &&
'opacity-50 cursor-not-allowed dark:hover:bg-blue-120',
)}
>
<Icon className="fill-blue-80 dark:fill-blue-50" size="md" />
</IconButton>
</TooltipTrigger>

<TooltipBody className="break-normal max-w-[250px] text-center">
{tooltip}
</TooltipBody>
</Tooltip>
);
};

export default BlueIconButton;
79 changes: 68 additions & 11 deletions apps/tangle-dapp/containers/LsMyPoolsTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,22 @@ import {
} from '@tanstack/react-table';
import { Table } from '../../../libs/webb-ui-components/src/components/Table';
import { Pagination } from '../../../libs/webb-ui-components/src/components/Pagination';
import { LsPool } from '../constants/liquidStaking/types';
import { LsPool, LsProtocolId } from '../constants/liquidStaking/types';
import {
ActionsDropdown,
Avatar,
AvatarGroup,
Button,
IconButton,
TANGLE_DOCS_URL,
Typography,
} from '@webb-tools/webb-ui-components';
import TokenAmountCell from '../components/tableCells/TokenAmountCell';
import pluralize from '../utils/pluralize';
import { ArrowRight } from '@webb-tools/icons';
import {
ArrowRight,
SubtractCircleLineIcon,
TokenIcon,
} from '@webb-tools/icons';
import useLsPools from '../data/liquidStaking/useLsPools';
import useSubstrateAddress from '../hooks/useSubstrateAddress';
import { BN } from '@polkadot/util';
Expand All @@ -32,11 +35,14 @@ import { GlassCard, TableStatus } from '../components';
import PercentageCell from '../components/tableCells/PercentageCell';
import { EMPTY_VALUE_PLACEHOLDER } from '../constants';
import { ActionItemType } from '@webb-tools/webb-ui-components/components/ActionsDropdown/types';
import { MinusCircledIcon } from '@radix-ui/react-icons';
import { useLsStore } from '../data/liquidStaking/useLsStore';
import BlueIconButton from '../components/BlueIconButton';
import getLsProtocolDef from '../utils/liquidStaking/getLsProtocolDef';
import useIsAccountConnected from '../hooks/useIsAccountConnected';

type MyLsPoolRow = LsPool & {
myStake: BN;
lsProtocolId: LsProtocolId;
isRoot: boolean;
isNominator: boolean;
isBouncer: boolean;
Expand All @@ -45,8 +51,10 @@ type MyLsPoolRow = LsPool & {
const COLUMN_HELPER = createColumnHelper<MyLsPoolRow>();

const LsMyPoolsTable: FC = () => {
const isAccountConnected = useIsAccountConnected();
const substrateAddress = useSubstrateAddress();
const [sorting, setSorting] = useState<SortingState>([]);
const { setIsStaking, setLsPoolId, lsPoolId, isStaking } = useLsStore();

const [{ pageIndex, pageSize }, setPagination] = useState({
pageIndex: 0,
Expand Down Expand Up @@ -84,11 +92,16 @@ const LsMyPoolsTable: FC = () => {
isRoot: lsPool.ownerAddress === substrateAddress,
isNominator: lsPool.nominatorAddress === substrateAddress,
isBouncer: lsPool.bouncerAddress === substrateAddress,
};
// TODO: Obtain which protocol this pool is associated with. For the parachain, there'd need to be some query to see what pools are associated with which parachain protocols. For Tangle networks, it's simply its own protocol. For now, using dummy data.
lsProtocolId: LsProtocolId.TANGLE_LOCAL,
} satisfies MyLsPoolRow;
});
}, []);

const handleUnstakeClick = useCallback((poolId: number) => {}, []);
const handleUnstakeClick = useCallback((poolId: number) => {
setIsStaking(false);
setLsPoolId(poolId);
}, []);

const columns = [
COLUMN_HELPER.accessor('id', {
Expand All @@ -103,6 +116,14 @@ const LsMyPoolsTable: FC = () => {
</Typography>
),
}),
COLUMN_HELPER.accessor('lsProtocolId', {
header: () => 'Protocol',
cell: (props) => {
const lsProtocol = getLsProtocolDef(props.getValue());

return <TokenIcon name={lsProtocol.chainIconFileName} />;
},
}),
COLUMN_HELPER.accessor('ownerAddress', {
header: () => 'Owner',
cell: (props) => (
Expand Down Expand Up @@ -161,36 +182,57 @@ const LsMyPoolsTable: FC = () => {
if (props.row.original.isNominator) {
actionItems.push({
label: 'Update Nominations',
// TODO: Implement onClick handler.
onClick: () => void 0,
});
}

if (props.row.original.isBouncer) {
actionItems.push({
label: 'Update Commission',
// TODO: Implement onClick handler.
onClick: () => void 0,
});
}

if (props.row.original.isRoot) {
actionItems.push({
label: 'Update Roles',
// TODO: Implement onClick handler.
onClick: () => void 0,
});
}

// Sanity check against logic errors.
if (hasAnyRole) {
assert(actionItems.length > 0);
}

// If the user has any role in the pool, show the short button style
// to avoid taking up too much space.
const isShortButtonStyle = hasAnyRole;

// Disable the stake button if the pool is currently selected,
// and the active intent is to unstake.
const isUnstakeDisabled =
lsPoolId === props.row.original.id && !isStaking;

return (
<div className="flex justify-end">
{isShortButtonStyle ? (
<IconButton tooltip="Unstake">
<MinusCircledIcon />
</IconButton>
<BlueIconButton
isDisabled={isUnstakeDisabled}
onClick={() => handleUnstakeClick(props.row.original.id)}
tooltip="Unstake"
Icon={SubtractCircleLineIcon}
/>
) : (
<Button rightIcon={<ArrowRight />} variant="utility">
<Button
isDisabled={isUnstakeDisabled}
onClick={() => handleUnstakeClick(props.row.original.id)}
rightIcon={<ArrowRight />}
variant="utility"
>
Unstake
</Button>
)}
Expand Down Expand Up @@ -224,7 +266,22 @@ const LsMyPoolsTable: FC = () => {
enableSortingRemoval: false,
});

if (rows.length === 0) {
// TODO: Missing error and loading state. Should ideally abstract all these states into an abstract Table component, since it's getting reused in multiple places.
if (!isAccountConnected) {
return (
<TableStatus
title="Connect a wallet to continue"
description="Once you've connected an account, you'll be able to see and manage your liquid staking pools here."
icon="🔍"
buttonText="Learn More"
buttonProps={{
// TODO: Link to liquid staking pools docs page once implemented.
href: TANGLE_DOCS_URL,
target: '_blank',
}}
/>
);
} else if (rows.length === 0) {
return (
<TableStatus
title="No active pools"
Expand Down
9 changes: 9 additions & 0 deletions apps/tangle-dapp/hooks/useIsAccountConnected.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { useActiveAccount } from '@webb-tools/api-provider-environment/WebbProvider/subjects';

const useIsAccountConnected = (): boolean => {
const [activeAccount] = useActiveAccount();

return activeAccount?.address !== undefined;
};

export default useIsAccountConnected;
10 changes: 10 additions & 0 deletions libs/icons/src/SubtractCircleLineIcon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { createIcon } from './create-icon';
import { IconBase } from './types';

export const SubtractCircleLineIcon = (props: IconBase) => {
return createIcon({
...props,
d: 'M7.00065 14.1654C3.31875 14.1654 0.333984 11.1806 0.333984 7.4987C0.333984 3.8168 3.31875 0.832031 7.00065 0.832031C10.6825 0.832031 13.6673 3.8168 13.6673 7.4987C13.6673 11.1806 10.6825 14.1654 7.00065 14.1654ZM7.00065 12.832C9.94619 12.832 12.334 10.4442 12.334 7.4987C12.334 4.55318 9.94619 2.16536 7.00065 2.16536C4.05513 2.16536 1.66732 4.55318 1.66732 7.4987C1.66732 10.4442 4.05513 12.832 7.00065 12.832ZM3.66732 6.83203H10.334V8.16536H3.66732V6.83203Z',
displayName: 'SubtractCircleLineIcon',
});
};
1 change: 1 addition & 0 deletions libs/icons/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,7 @@ export { default as WalletPayIcon } from './WalletPayIcon';
export * from './WaterDropletIcon';
export { default as WebbLogoIcon } from './WebbLogoIcon';
export * from './YouTubeFill';
export * from './SubtractCircleLineIcon';

// Wallet icons
export * from './wallets';
Expand Down

0 comments on commit 8d50504

Please sign in to comment.