diff --git a/src/components/CopyableAddress.tsx b/src/components/CopyableAddress.tsx index eaee6e9..d05f590 100644 --- a/src/components/CopyableAddress.tsx +++ b/src/components/CopyableAddress.tsx @@ -1,44 +1,102 @@ import React, { useState } from 'react'; -import { Tooltip, Typography } from '@mui/material'; +import { IconButton, Link, Stack, Tooltip, Typography } from '@mui/material'; +import ContentCopyIcon from '@mui/icons-material/ContentCopy'; +import CheckIcon from '@mui/icons-material/Check'; import { FONTS } from '../theme'; +import { getExplorerUrl, type ExplorerType } from '../utils/explorer'; const shortAddr = (addr: string) => addr.length > 10 ? `${addr.slice(0, 4)}..${addr.slice(-4)}` : addr; interface CopyableAddressProps { address: string; + chain?: string | null; + type?: ExplorerType; + display?: string; fontSize?: string; color?: string; + showCopy?: boolean; } const CopyableAddress: React.FC = ({ address, + chain, + type = 'address', + display, fontSize = '0.7rem', color = 'text.secondary', + showCopy = true, }) => { const [copied, setCopied] = useState(false); + const explorerUrl = getExplorerUrl(chain, type, address); + const text = display ?? shortAddr(address); - const handleClick = () => { + const handleCopy = (e: React.MouseEvent) => { + e.preventDefault(); + e.stopPropagation(); navigator.clipboard.writeText(address); setCopied(true); setTimeout(() => setCopied(false), 1500); }; + const label = explorerUrl ? ( + e.stopPropagation()} + sx={{ + fontFamily: FONTS.mono, + fontSize, + color, + textDecoration: 'none', + '&:hover': { textDecoration: 'underline', color: 'primary.main' }, + }} + > + {text} + + ) : ( + + {text} + + ); + return ( - - {shortAddr(address)} - + {label} + {showCopy && ( + + {copied ? ( + + ) : ( + + )} + + )} + ); }; diff --git a/src/components/dashboard/EventFeed.tsx b/src/components/dashboard/EventFeed.tsx index 72d10b8..b61547f 100644 --- a/src/components/dashboard/EventFeed.tsx +++ b/src/components/dashboard/EventFeed.tsx @@ -4,6 +4,7 @@ import { Box, Button, Chip, Stack, Typography, useTheme } from '@mui/material'; import KeyboardArrowUpIcon from '@mui/icons-material/KeyboardArrowUp'; import { useLatestEvents } from '../../api'; import { FONTS } from '../../theme'; +import { getExplorerUrl } from '../../utils'; import CopyableAddress from '../CopyableAddress'; import { EventFeedSkeleton } from './Skeletons'; @@ -111,10 +112,19 @@ const EventFeed: React.FC = () => { }} /> #{event.blockNumber} @@ -155,7 +165,7 @@ const EventFeed: React.FC = () => { )} {event.minerHotkey && ( - + )} {event.taoAmount && ( { )} {event.reservedUntil && ( until #{event.reservedUntil} diff --git a/src/components/dashboard/MinerRatesTable.tsx b/src/components/dashboard/MinerRatesTable.tsx index 33494c7..16abe09 100644 --- a/src/components/dashboard/MinerRatesTable.tsx +++ b/src/components/dashboard/MinerRatesTable.tsx @@ -451,7 +451,7 @@ const MinerRatesTable: React.FC = () => { color: 'text.secondary', }} > - + ); diff --git a/src/components/dashboard/SwapTracker.tsx b/src/components/dashboard/SwapTracker.tsx index 54360c4..c81dc7f 100644 --- a/src/components/dashboard/SwapTracker.tsx +++ b/src/components/dashboard/SwapTracker.tsx @@ -255,7 +255,8 @@ const SwapTracker: React.FC = () => { color: 'text.secondary', }} > - User: + User:{' '} + )} {swap.minerHotkey && ( @@ -268,7 +269,8 @@ const SwapTracker: React.FC = () => { color: 'text.secondary', }} > - Miner: + Miner:{' '} + )} diff --git a/src/pages/SwapDetailPage.tsx b/src/pages/SwapDetailPage.tsx index 1c648d0..2856e50 100644 --- a/src/pages/SwapDetailPage.tsx +++ b/src/pages/SwapDetailPage.tsx @@ -18,6 +18,7 @@ import { chainSymbol, formatBlockEstimate, } from '../utils/format'; +import { getExplorerUrl } from '../utils'; type TimelineStep = { label: string; @@ -215,15 +216,35 @@ const SwapDetailPage: React.FC = () => { > {step.label} - - {step.block ? `Block #${step.block}` : '\u2014'} - + {step.block ? ( + + Block #{step.block} + + ) : ( + + {'\u2014'} + + )} ); })} @@ -253,7 +274,24 @@ const SwapDetailPage: React.FC = () => { color: 'text.secondary', }} > - Block #{swap.timeoutBlock} + + Block #{swap.timeoutBlock} + {!isTimedOut && swap.status !== 'COMPLETED' && swap.initiatedBlock && ( @@ -282,11 +320,15 @@ const SwapDetailPage: React.FC = () => { label="Source TX" value={swap.sourceTxHash || '\u2014'} copyable={!!swap.sourceTxHash} + chain={swap.sourceChain} + type="tx" /> @@ -297,19 +339,31 @@ const SwapDetailPage: React.FC = () => { Participants {swap.userAddress && ( - + )} {swap.userSourceAddress && ( - + )} {swap.userDestAddress && ( - + )} {swap.minerHotkey && ( - + )} {swap.minerSourceAddress && ( - + )} @@ -340,10 +394,22 @@ const SwapDetailPage: React.FC = () => { }} /> #{event.blockNumber} @@ -434,7 +500,9 @@ const LabelValue: React.FC<{ label: string; value: string; copyable?: boolean; -}> = ({ label, value, copyable }) => ( + chain?: string | null; + type?: 'address' | 'tx' | 'block'; +}> = ({ label, value, copyable, chain, type }) => ( {copyable ? ( - + ) : ( ); -const LabelAddr: React.FC<{ label: string; address: string }> = ({ - label, - address, -}) => ( +const LabelAddr: React.FC<{ + label: string; + address: string; + chain?: string | null; +}> = ({ label, address, chain }) => ( = ({ > {label} - + ); diff --git a/src/utils/explorer.ts b/src/utils/explorer.ts new file mode 100644 index 0000000..6820ffa --- /dev/null +++ b/src/utils/explorer.ts @@ -0,0 +1,26 @@ +export type ExplorerChain = 'btc' | 'tao'; +export type ExplorerType = 'address' | 'tx' | 'block'; + +const builders: Record< + ExplorerChain, + Partial string>> +> = { + btc: { + address: (v) => `https://www.blockchain.com/btc/address/${v}`, + tx: (v) => `https://www.blockchain.com/btc/tx/${v}`, + }, + tao: { + address: (v) => `https://taostats.io/account/${v}`, + block: (v) => `https://taostats.io/block/${v}`, + }, +}; + +export const getExplorerUrl = ( + chain: string | undefined | null, + type: ExplorerType, + value: string | undefined | null, +): string | null => { + if (!chain || !value) return null; + const key = chain.toLowerCase() as ExplorerChain; + return builders[key]?.[type]?.(value) ?? null; +}; diff --git a/src/utils/index.ts b/src/utils/index.ts index 16c5b2b..7874fd9 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -1 +1,2 @@ export * from './format'; +export * from './explorer';