Skip to content

Commit

Permalink
feat(tangle-dapp): Add Restake Withdraw Flow (#2508)
Browse files Browse the repository at this point in the history
Co-authored-by: drewstone <[email protected]>
  • Loading branch information
AtelyPham and drewstone authored Aug 19, 2024
1 parent bb30865 commit 3d9764c
Show file tree
Hide file tree
Showing 19 changed files with 1,024 additions and 54 deletions.
2 changes: 1 addition & 1 deletion apps/tangle-dapp/app/restake/ModalContent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ const ModalContent = ({
isCenter
{...props}
className={twMerge(
'w-full h-full p-4 max-w-xl max-h-[var(--restake-modal-max-height)]',
'w-full h-full max-w-xl max-h-[var(--restake-modal-max-height)]',
className,
)}
>
Expand Down
2 changes: 1 addition & 1 deletion apps/tangle-dapp/app/restake/RestakeTabs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import TabsList from './TabsList';

export type TabsListProps = PropsOf<'ul'>;

export const tabs = ['deposit', 'stake', 'unstake'] as const;
export const tabs = ['deposit', 'stake', 'unstake', 'withdraw'] as const;

const RestakeTabs = (props: TabsListProps) => {
const pathname = usePathname();
Expand Down
22 changes: 22 additions & 0 deletions apps/tangle-dapp/app/restake/TableCell.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { Typography } from '@webb-tools/webb-ui-components/typography/Typography';
import type { ComponentProps } from 'react';
import { twMerge } from 'tailwind-merge';

export default function TableCell({
className,
children,
variant = 'body2',
...props
}: Partial<ComponentProps<typeof Typography>>) {
return (
<Typography
component="span"
fw="semibold"
{...props}
variant={variant}
className={twMerge('text-mono-120 dark:text-mono-100', className)}
>
{children}
</Typography>
);
}
29 changes: 3 additions & 26 deletions apps/tangle-dapp/app/restake/unstake/UnstakeRequestTable.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,8 @@ import { TimeFillIcon } from '@webb-tools/icons/TimeFillIcon';
import { CheckBox } from '@webb-tools/webb-ui-components/components/CheckBox';
import { fuzzyFilter } from '@webb-tools/webb-ui-components/components/Filter/utils';
import { Table } from '@webb-tools/webb-ui-components/components/Table';
import { Typography } from '@webb-tools/webb-ui-components/typography/Typography';
import cx from 'classnames';
import { type ComponentProps, useMemo } from 'react';
import { twMerge } from 'tailwind-merge';
import { useMemo } from 'react';
import { formatUnits } from 'viem';

import { useRestakeContext } from '../../../context/RestakeContext';
Expand All @@ -26,9 +24,10 @@ import useRestakeCurrentRound from '../../../data/restake/useRestakeCurrentRound
import type { DelegatorUnstakeRequest } from '../../../types/restake';
import type { IdentityType } from '../../../utils/polkadot';
import AvatarWithText from '../AvatarWithText';
import TableCell from '../TableCell';
import { calculateTimeRemaining } from '../utils';
import type { UnstakeRequestTableData } from './types';
import UnstakeRequestTableActions from './UnstakeRequestTableActions';
import { calculateTimeRemaining } from './utils';

const columnsHelper = createColumnHelper<UnstakeRequestTableData>();

Expand Down Expand Up @@ -186,28 +185,6 @@ const UnstakeRequestTable = ({

export default UnstakeRequestTable;

/**
* @internal
*/
function TableCell({
className,
children,
variant = 'body2',
...props
}: Partial<ComponentProps<typeof Typography>>) {
return (
<Typography
component="span"
fw="semibold"
{...props}
variant={variant}
className={twMerge('text-mono-120 dark:text-mono-100', className)}
>
{children}
</Typography>
);
}

function getId({
assetId,
operatorAccountId,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ import {
} from '../../../data/restake/RestakeTx/base';
import useRestakeTx from '../../../data/restake/useRestakeTx';
import useRestakeTxEventHandlersWithNoti from '../../../data/restake/useRestakeTxEventHandlersWithNoti';
import { isScheduledRequestReady } from '../utils';
import type { UnstakeRequestTableData } from './types';
import { isUnstakeRequestReady } from './utils';

type Props = {
allRequests: UnstakeRequestTableData[];
Expand Down Expand Up @@ -89,7 +89,7 @@ const UnstakeRequestTableActions = ({
if (allRequests.length === 0) return false;

return allRequests.some(({ timeRemaining }) => {
return isUnstakeRequestReady(timeRemaining);
return isScheduledRequestReady(timeRemaining);
});
}, [allRequests]);

Expand All @@ -115,7 +115,7 @@ const UnstakeRequestTableActions = ({
isFullWidth
onClick={handleExecuteUnstake}
>
Execute All Executable Requests
Execute All
</Button>
</>
);
Expand Down
16 changes: 0 additions & 16 deletions apps/tangle-dapp/app/restake/unstake/utils.ts

This file was deleted.

16 changes: 16 additions & 0 deletions apps/tangle-dapp/app/restake/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
export function calculateTimeRemaining(
currentRound: number,
requestedRound: number,
delay: number | null,
) {
if (typeof delay !== 'number') return -1;

const roundPassed = currentRound - requestedRound;
if (roundPassed >= delay) return 0;

return delay - roundPassed;
}

export function isScheduledRequestReady(timeRemaining: number) {
return timeRemaining === 0;
}
30 changes: 30 additions & 0 deletions apps/tangle-dapp/app/restake/withdraw/TxInfo.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import FeeDetails from '@webb-tools/webb-ui-components/components/FeeDetails';
import type { FeeItem } from '@webb-tools/webb-ui-components/components/FeeDetails/types';
import { useMemo } from 'react';

import useRestakeConsts from '../../../data/restake/useRestakeConsts';

const TxInfo = () => {
const { leaveDelegatorsDelay } = useRestakeConsts();

const items = useMemo<FeeItem[]>(
() => [
// TODO: Add fee value
{
name: 'Fee',
},
{
name: 'Withdraw Delay',
value:
typeof leaveDelegatorsDelay === 'number'
? `${leaveDelegatorsDelay} rounds`
: null,
},
],
[leaveDelegatorsDelay],
);

return <FeeDetails isDisabledBgColor disabled isDefaultOpen items={items} />;
};

export default TxInfo;
144 changes: 144 additions & 0 deletions apps/tangle-dapp/app/restake/withdraw/WithdrawModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
import { DEFAULT_DECIMALS } from '@webb-tools/dapp-config/constants';
import { TokenIcon } from '@webb-tools/icons/TokenIcon';
import { ListItem } from '@webb-tools/webb-ui-components/components/ListCard/ListItem';
import { Typography } from '@webb-tools/webb-ui-components/typography/Typography';
import { useMemo } from 'react';
import { twMerge } from 'tailwind-merge';
import { formatUnits } from 'viem';

import { useRestakeContext } from '../../../context/RestakeContext';
import { DelegatorInfo } from '../../../types/restake';
import ModalContent from '../ModalContent';
import ModalContentList from '../ModalContentList';

type Props = {
delegatorInfo: DelegatorInfo | null;
isOpen: boolean;
onClose: () => void;
onItemSelected: (item: {
assetId: string;
amount: bigint;
formattedAmount: string;
}) => void;
};

const WithdrawModal = ({
delegatorInfo,
isOpen,
onClose,
onItemSelected,
}: Props) => {
const { assetMap } = useRestakeContext();

// Aggregate the delegations based on the operator account id and asset id
const deposits = useMemo(() => {
if (!delegatorInfo?.deposits) {
return [];
}

return Object.entries(delegatorInfo.deposits).map(
([assetId, { amount }]) => ({
assetId,
amount,
}),
);
}, [delegatorInfo]);

return (
<ModalContent
isOpen={isOpen}
title="Select Withdrawal Asset"
description="Select the asset you want to withdraw"
onInteractOutside={onClose}
>
<ModalContentList
title="Select Withdrawal Asset"
items={deposits}
onClose={onClose}
overrideSearchInputProps={{
id: 'search-withdraw-asset',
placeholder: 'Search Asset to Withdraw',
}}
searchFilter={({ amount, assetId }, searchText) => {
if (!searchText) {
return true;
}

const asset = assetMap[assetId];
const assetSymbol = asset?.symbol || 'Unknown';

return (
assetSymbol.toLowerCase().includes(searchText.toLowerCase()) ||
amount.toString().includes(searchText)
);
}}
renderEmpty={{
title: 'No Asset Found',
description:
'You can try to deposit or delegate an asset to an operator.',
}}
renderItem={(item) => {
const { amount, assetId } = item;
const asset = assetMap[assetId];

const decimals = asset?.decimals || DEFAULT_DECIMALS;
const assetSymbol = asset?.symbol || 'Unknown';

const fmtAmount = formatUnits(amount, decimals);

return (
<ListItem
className={twMerge(
'cursor-pointer max-w-none dark:bg-transparent',
'flex items-center justify-between px-4',
)}
key={assetId}
onClick={() =>
onItemSelected({
...item,
formattedAmount: fmtAmount,
})
}
>
<div className="flex items-center gap-2">
<TokenIcon size="xl" name={assetSymbol} />

<div>
<Typography variant="h5" fw="bold">
{assetSymbol}
</Typography>

<Typography
variant="body2"
className="text-mono-120 dark:text-mono-100"
>
Asset ID: {assetId}
</Typography>
</div>
</div>

<div>
<Typography ta="right" variant="h5" fw="bold">
{fmtAmount}
</Typography>

{asset.poolId && (
<Typography
ta="right"
variant="body3"
fw="semibold"
className="!text-mono-100 mt-1"
>
Pool ID: {asset.poolId}
</Typography>
)}
</div>
</ListItem>
);
}}
/>
</ModalContent>
);
};

export default WithdrawModal;
Loading

0 comments on commit 3d9764c

Please sign in to comment.