Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
0c923aa
feat: init recent activity behaviour
hetfly Apr 9, 2025
283768b
feat: update dropdown layout
hetfly Apr 10, 2025
5b88efb
feat: add activity icon
hetfly Apr 10, 2025
2c0031b
feat: add history item component
hetfly Apr 10, 2025
15f886c
feat: init history list
hetfly Apr 11, 2025
3dfcb7f
feat: init icon only tx history
hetfly Apr 13, 2025
b920b7b
feat: add timestamp to mm transactions
hetfly Apr 13, 2025
c3a47a5
chore: update locales
hetfly Apr 14, 2025
82adeb5
feat: add tx status component
hetfly Apr 14, 2025
2118b5b
feat: add mm history item
hetfly Apr 14, 2025
debf894
feat: add possibility to cancel intent from history and remove it fro…
hetfly Apr 14, 2025
c6f2168
feat: add recent tx pending hook and animate activity icon
hetfly Apr 14, 2025
603b495
feat: combine icon only and xtransaction in history, update updater
hetfly Apr 16, 2025
f89ba17
chore: update locales
hetfly Apr 16, 2025
56bc4b9
feat: add collateral transaction items
hetfly Apr 17, 2025
041fbb2
chore: cleanup
hetfly Apr 17, 2025
72f7f27
fix: ensure positive token amount
hetfly Apr 17, 2025
7408932
feat: add loan transaction history items
hetfly Apr 18, 2025
2c12c62
feat: add LP transaction items
hetfly Apr 19, 2025
3eede8d
feat: update LP items logos
hetfly Apr 19, 2025
e1186ca
feat: add rewards and savings history items
hetfly Apr 20, 2025
10e6918
feat: add tracker links
hetfly Apr 22, 2025
0c8c653
feat: improve recent activity ux
hetfly Apr 29, 2025
cba1862
feat: make suivision default tracker for sui
hetfly Apr 29, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 59 additions & 2 deletions apps/web/src/app/components/Header/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,20 @@ import { Button, IconButton } from '@/app/components/Button';
import Logo from '@/app/components/Logo';
import { DropdownPopper } from '@/app/components/Popover';
import { Typography } from '@/app/theme';
import RecentActivityIcon from '@/assets/icons/activity.svg';
import CopyIcon from '@/assets/icons/copy.svg';
import WalletIcon from '@/assets/icons/wallet.svg';
import { useWalletModalToggle } from '@/store/application/hooks';
import { useAllTransactions } from '@/store/transactions/hooks';
import { shortenAddress } from '@/utils';

import { useIsAnyTxPending } from '@/hooks/useCombinedTransactions';
import { useSignedInWallets } from '@/hooks/useWallets';
import { xChainMap } from '@balancednetwork/xwagmi';
import { bnJs } from '@balancednetwork/xwagmi';
import { Placement } from '@popperjs/core';
import { UseQueryResult, useQuery } from '@tanstack/react-query';
import RecentActivity from '../RecentActivity';
import { MouseoverTooltip } from '../Tooltip';
import Wallet from '../Wallet';
import { notificationCSS } from '../Wallet/ICONWallets/utils';
Expand Down Expand Up @@ -53,6 +56,29 @@ const WalletButtonWrapper = styled(Box)<{ $hasnotification?: boolean }>`
}
`;

const RecentActivityButtonWrapper = styled(Box)<{ $hasnotification?: boolean }>`
position: relative;
margin-left: 15px;
${({ $hasnotification }) => ($hasnotification ? notificationCSS : '')}
&::before, &::after {
left: 7px;
top: 13px;
${({ theme }) => `background-color: ${theme.colors.bg5}`};
}
`;

const SpinningIcon = styled(RecentActivityIcon)`
@keyframes spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(-720deg);
}
}
animation: spin 2s ease-in-out infinite;
`;

export const StyledAddress = styled(Typography)`
&:hover {
color: #2fccdc;
Expand Down Expand Up @@ -137,10 +163,12 @@ export default function Header(props: { title?: string; className?: string }) {
const wallets = useSignedInWallets();
const { data: claimableICX } = useClaimableICX();
const hasBTCB = useHasBTCB();

const isAnyTxPending = useIsAnyTxPending();
const walletButtonRef = React.useRef<HTMLElement>(null);
const [anchor, setAnchor] = React.useState<HTMLElement | null>(null);

const walletButtonRef = React.useRef<HTMLElement>(null);
const recentActivityButtonRef = React.useRef<HTMLElement>(null);
const [recentActivityAnchor, setRecentActivityAnchor] = React.useState<HTMLElement | null>(null);

const toggleWalletMenu = () => {
setAnchor(anchor ? null : walletButtonRef.current);
Expand All @@ -155,6 +183,11 @@ export default function Header(props: { title?: string; className?: string }) {
}
};

const toggleRecentActivityMenu = () => {
setRecentActivityAnchor(recentActivityAnchor ? null : recentActivityButtonRef.current);
};
const closeRecentActivityMenu = useCallback(() => setRecentActivityAnchor(null), []);

return (
<header className={className}>
<Flex justifyContent="space-between">
Expand Down Expand Up @@ -235,6 +268,30 @@ export default function Header(props: { title?: string; className?: string }) {
</div>
</ClickAwayListener>
</WalletButtonWrapper>

<RecentActivityButtonWrapper>
<ClickAwayListener onClickAway={closeRecentActivityMenu}>
<div>
<IconButton ref={recentActivityButtonRef} onClick={toggleRecentActivityMenu}>
{isAnyTxPending ? (
<SpinningIcon width="26" height="26" />
) : (
<RecentActivityIcon width="26" height="26" />
)}
</IconButton>

<DropdownPopper
show={Boolean(recentActivityAnchor)}
anchorEl={recentActivityAnchor}
placement="bottom-end"
offset={[0, 15]}
zIndex={5050}
>
<RecentActivity />
</DropdownPopper>
</div>
</ClickAwayListener>
</RecentActivityButtonWrapper>
</Flex>
)}
</Flex>
Expand Down
47 changes: 39 additions & 8 deletions apps/web/src/app/components/PoolLogoWithNetwork/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { xChainMap } from '@balancednetwork/xwagmi';
import { ChainLogo } from '../ChainLogo';
import CurrencyLogo from '../CurrencyLogo';

const IconWrapper = styled(Box)<{ $respoVersion?: boolean }>`
const IconWrapper = styled(Box)<{ $respoVersion?: boolean; $compactVersion?: boolean }>`
width: 48px;
height: 48px;
border-radius: 50%;
Expand Down Expand Up @@ -39,9 +39,17 @@ const IconWrapper = styled(Box)<{ $respoVersion?: boolean }>`
}
`}
`};

${({ $compactVersion }) =>
$compactVersion &&
css`
width: 25px;
height: 25px;
background-color: rgb(12, 42, 77);
`}
`;

const NetworkWrap = styled.div`
const NetworkWrap = styled.div<{ $compactVersion?: boolean }>`
position: absolute;
border-radius: 50%;
width: 14px;
Expand All @@ -58,34 +66,57 @@ const NetworkWrap = styled.div`
top: 0;
left: 0;
}

${({ $compactVersion }) =>
$compactVersion &&
css`
width: 10px;
height: 10px;
outline: 1px solid #0c2a4d;
bottom: -3px;
margin-left: -6px;

img {
width: 10px;
height: 10px;
}
`}
`;

export const PoolLogoWrapper = styled(Box)`
position: relative;
export const PoolLogoWrapper = styled(Box)<{ $compactVersion?: boolean }>`
position: relative;
display: flex;
min-width: 80px;

${({ $compactVersion }) =>
$compactVersion &&
css`
min-width: 40px;
`}
`;

function PoolLogoWithNetwork({
baseCurrency,
quoteCurrency,
respoVersion,
chainId,
compactVersion,
}: {
baseCurrency: Currency;
quoteCurrency: Currency;
respoVersion?: boolean;
chainId: XChainId;
compactVersion?: boolean;
}) {
return (
<PoolLogoWrapper>
<IconWrapper $respoVersion={respoVersion}>
<PoolLogoWrapper $compactVersion={compactVersion}>
<IconWrapper $respoVersion={respoVersion} $compactVersion={compactVersion}>
<CurrencyLogo currency={baseCurrency} />
</IconWrapper>
<IconWrapper ml={-2} $respoVersion={respoVersion}>
<IconWrapper ml={-2} $respoVersion={respoVersion} $compactVersion={compactVersion}>
<CurrencyLogo currency={quoteCurrency} />
</IconWrapper>
<NetworkWrap>
<NetworkWrap $compactVersion={compactVersion}>
<ChainLogo chain={xChainMap[chainId]} size="14px" />
</NetworkWrap>
</PoolLogoWrapper>
Expand Down
106 changes: 106 additions & 0 deletions apps/web/src/app/components/RecentActivity/HistoryItem.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
import { MMTransaction } from '@/store/transactions/useMMTransactionStore';
import { XTransaction, XTransactionType, getTxTrackerLink } from '@balancednetwork/xwagmi';
import { motion } from 'framer-motion';
import React from 'react';
import BridgeTransaction from './transactions/BridgeTransaction';
import CollateralTransaction from './transactions/CollateralTransaction';
import DepositXTokenTransaction from './transactions/DepositXTokenTransaction';
import LPStakeTransaction from './transactions/LPStakeTransaction';
import LPTransaction from './transactions/LPTransaction';
import LoanTransaction from './transactions/LoanTransaction';
import MMSwapTransaction from './transactions/MMSwapTransaction';
import RewardsFeesTransaction from './transactions/RewardsFeesTransaction';
import RewardsLPTransaction from './transactions/RewardsLPTransaction';
import RewardsSavingsTransaction from './transactions/RewardsSavingsTransaction';
import SavingsTransaction from './transactions/SavingsTransaction';
import SwapTransaction from './transactions/SwapTransaction';

interface HistoryItemProps {
transaction: MMTransaction | XTransaction;
isMMTransaction: (transaction: MMTransaction | XTransaction) => transaction is MMTransaction;
}

const HistoryItem: React.FC<HistoryItemProps> = ({ transaction, isMMTransaction }) => {
const hash = transaction.id.split('/')[1] || transaction.id;
const xChainId = isMMTransaction(transaction) ? transaction.fromAmount.currency.xChainId : transaction.sourceChainId;
const trackerLink = getTxTrackerLink(hash, xChainId);

const handleClick = () => {
if (trackerLink) {
window.open(trackerLink, '_blank');
}
};

const renderContent = () => {
if (isMMTransaction(transaction)) {
return <MMSwapTransaction transaction={transaction} />;
}

switch (transaction.type) {
//Swap
case XTransactionType.SWAP:
case XTransactionType.SWAP_ON_ICON:
return <SwapTransaction transaction={transaction} />;

//Bridge
case XTransactionType.BRIDGE:
return <BridgeTransaction transaction={transaction} />;

// Collateral
case XTransactionType.DEPOSIT_ON_ICON:
case XTransactionType.DEPOSIT:
case XTransactionType.WITHDRAW:
case XTransactionType.WITHDRAW_ON_ICON:
return <CollateralTransaction transaction={transaction} />;

//Loan
case XTransactionType.BORROW:
case XTransactionType.REPAY:
case XTransactionType.BORROW_ON_ICON:
case XTransactionType.REPAY_ON_ICON:
return <LoanTransaction transaction={transaction} />;

//Liquidity
case XTransactionType.LP_REMOVE_LIQUIDITY:
case XTransactionType.LP_ADD_LIQUIDITY:
return <LPTransaction transaction={transaction} />;
case XTransactionType.LP_DEPOSIT_XTOKEN:
case XTransactionType.LP_WITHDRAW_XTOKEN:
return <DepositXTokenTransaction transaction={transaction} />;
case XTransactionType.LP_UNSTAKE:
case XTransactionType.LP_STAKE:
return <LPStakeTransaction transaction={transaction} />;

//Savings
case XTransactionType.SAVINGS_LOCK_BNUSD:
case XTransactionType.SAVINGS_UNLOCK_BNUSD:
return <SavingsTransaction transaction={transaction} />;

//Rewards
case XTransactionType.LP_CLAIM_REWARDS:
return <RewardsLPTransaction transaction={transaction} />;
case XTransactionType.SAVINGS_CLAIM_REWARDS:
return <RewardsSavingsTransaction transaction={transaction} />;
case XTransactionType.CLAIM_NETWORK_FEES:
return <RewardsFeesTransaction transaction={transaction} />;

default:
return <div>Unknown Transaction Type - {transaction.type}</div>;
}
};

return (
<motion.div
initial={{ opacity: 0, y: -20, height: 0 }}
animate={{ opacity: 1, y: 0, height: 'auto' }}
transition={{ duration: 0.3, ease: 'easeOut' }}
key={transaction.id}
onClick={handleClick}
style={{ cursor: trackerLink ? 'pointer' : 'default', pointerEvents: trackerLink ? 'auto' : 'none' }}
>
{renderContent()}
</motion.div>
);
};

export default HistoryItem;
58 changes: 58 additions & 0 deletions apps/web/src/app/components/RecentActivity/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { Typography } from '@/app/theme';
import { useCombinedTransactions } from '@/hooks/useCombinedTransactions';
import React from 'react';
import styled from 'styled-components';
import HistoryItem from './HistoryItem';

const Wrap = styled.div`
padding: 25px 0;
width: 400px;
max-width: calc(100vw - 4px);

ul {
max-height: 500px;
overflow-y: auto;
list-style: none;
padding: 0 25px;
margin: 0;
}
`;

const ListItem = styled.li`
padding: 20px 0;
display: block;
border-bottom: 1px solid ${({ theme }) => theme.colors.divider};

&:last-child {
border-bottom: none;
margin-bottom: 0;
}

`;

const RecentActivity: React.FC = () => {
const { transactions, isMMTransaction } = useCombinedTransactions();

return (
<Wrap>
<Typography variant="h2" px="25px">
Recent Activity
</Typography>
{transactions.length === 0 ? (
<Typography px="25px" mt="15px">
No activity to display.
</Typography>
) : (
<ul>
{transactions.map(transaction => (
<ListItem key={transaction.id}>
<HistoryItem transaction={transaction} isMMTransaction={isMMTransaction} />
</ListItem>
))}
</ul>
)}
</Wrap>
);
};

export default RecentActivity;
Loading