diff --git a/components/Paybutton/PaybuttonDetail.tsx b/components/Paybutton/PaybuttonDetail.tsx index e4a9a35b..bed10d55 100644 --- a/components/Paybutton/PaybuttonDetail.tsx +++ b/components/Paybutton/PaybuttonDetail.tsx @@ -9,6 +9,7 @@ import { XEC_NETWORK_ID, BCH_NETWORK_ID } from 'constants/index' import Image from 'next/image' import CopyIcon from '../../assets/copy-black.png' import Arrow from 'assets/right-arrow.png' +import { formatAddressWithEllipsis } from 'utils/index' export const paybuttonHasAddressOnNetwork = (paybutton: PaybuttonWithAddresses, networkId: number): boolean => paybutton.addresses.some(addr => addr.address.networkId === networkId) @@ -63,11 +64,7 @@ export default ({ paybutton, refreshPaybutton, listView }: IProps): JSX.Element {isCopied === item.address.address && (
Copied!
)} - {item.address.address.slice( - 0, - item.address.address.startsWith('bitcoin') ? 16 : 10 - )} - ...{item.address.address.slice(-5)} + {formatAddressWithEllipsis(item.address.address)}
{ const [localRefreshCount] = useState(tableRefreshCount) + const [copiedRowId, setCopiedRowId] = useState('') + const copyTimeoutRef = useRef(null) + + const handleCopyClick = useCallback(async (address: string, rowId: string): Promise => { + try { + await navigator.clipboard.writeText(address) + setCopiedRowId(rowId) + if (copyTimeoutRef.current != null) { + clearTimeout(copyTimeoutRef.current) + } + copyTimeoutRef.current = setTimeout(() => { + setCopiedRowId('') + copyTimeoutRef.current = null + }, 1200) + } catch (error) { + console.error('Copy failed:', error) + } + }, []) + + useEffect(() => { + return () => { + if (copyTimeoutRef.current != null) { + clearTimeout(copyTimeoutRef.current) + } + } + }, []) const columns = useMemo( () => [ @@ -106,19 +134,35 @@ export default ({ paybuttonId, addressSyncing, tableRefreshCount, timezone = mom accessor: 'address.address', shrinkable: true, Cell: (cellProps) => { + const address = cellProps.cell.value + const rowId = cellProps.row.id return ( -
- {cellProps.cell.value} +
+ {address} + {formatAddressWithEllipsis(address)} +
{ + e.stopPropagation() + e.preventDefault() + void handleCopyClick(address, rowId) + }} + > + copy address + + Copied! + +
) } } ], - [] + [copiedRowId, handleCopyClick] ) return ( - <> +
- +
) } diff --git a/components/Transaction/transaction.module.css b/components/Transaction/transaction.module.css index ecd8a7cc..d3aa0fd6 100644 --- a/components/Transaction/transaction.module.css +++ b/components/Transaction/transaction.module.css @@ -81,48 +81,94 @@ .copy_btn { position: relative; display: inline-block; - margin-left: 10px; + margin-left: 5px; cursor: pointer; display: flex; align-items: center; + justify-content: center; + flex: 0 0 auto; + min-width: 20px; } .copy_btn img { border-radius: 0 !important; - opacity: 0.5; + opacity: 0.6; } .copy_btn:hover img { opacity: 1; + filter: invert(60%) sepia(14%) saturate(5479%) hue-rotate(195deg) + brightness(100%) contrast(102%); } .tooltiptext2 { visibility: hidden; opacity: 0; - background-color: var(--secondary-bg-color); - color: var(--primary-text-color); + background-color: var(--accent-color); + color: #fff; text-align: center; padding: 2px 0px; border-radius: 5px; position: absolute; z-index: 1; width: 110px; - left: 100%; + right: 100%; flex-basis: 1; font-size: 12px; z-index: 99999999; box-sizing: border-box; - transition: all 100ms ease-in-out; - /* transform: translate(-50%, -80%); */ + transition: all 120ms ease-in-out; + transform: translateX(-6px); } -.copy_btn:hover .tooltiptext2, -.copy_btn:hover .tooltiptext2 { +.tooltiptext2Visible { visibility: visible; - transform: translateX(6px); opacity: 1; } +body[data-theme='dark'] .copy_btn img { + filter: invert(1) brightness(1.2); +} + +body[data-theme='dark'] .copy_btn:hover img { + filter: invert(60%) sepia(14%) saturate(5479%) hue-rotate(195deg) + brightness(100%) contrast(102%); + opacity: 1; +} + +.copy_btn:focus-visible { + outline: 2px solid var(--accent-color); + border-radius: 4px; +} + +.transactionsTable { + width: 100%; +} + +@media (max-width: 639px) { + .transactionsTable :global(.paybutton-table-ctn th:nth-child(1)), + .transactionsTable :global(.paybutton-table-ctn td:nth-child(1)) { + display: none; + } +} + +.addressFull { + display: inline; +} + +.addressShort { + display: none; +} + +@media (max-width: 1599px) { + .addressFull { + display: none; + } + .addressShort { + display: inline; + } +} + @media (max-width: 600px) { .transactiontable_header > div:first-child, .transaction_row > div:first-child { diff --git a/utils/index.ts b/utils/index.ts index 16a5fcd1..140a2729 100644 --- a/utils/index.ts +++ b/utils/index.ts @@ -13,6 +13,15 @@ export const removeAddressPrefix = function (addressString: string): string { return addressString } +export const formatAddressWithEllipsis = (addressString: string): string => { + const prefixLength = addressString.startsWith('bitcoin') ? 16 : 10 + const suffixLength = 5 + + if (addressString.length <= prefixLength + suffixLength) return addressString + + return `${addressString.slice(0, prefixLength)}...${addressString.slice(-suffixLength)}` +} + export const getAddressPrefix = function (addressString: string): NetworkSlugsType { try { const format = xecaddr.detectAddressFormat(addressString) diff --git a/yarn.lock b/yarn.lock index 6af26e03..335dd873 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2294,7 +2294,7 @@ chronik-client-cashtokens@^3.1.1-rc0: dependencies: "@types/ws" "^8.2.1" axios "^1.6.3" - ecashaddrjs "file:../../../.cache/yarn/v6/npm-chronik-client-cashtokens-3.1.1-rc0-e5e4a3538e8010b70623974a731bf2712506c5e3-integrity/node_modules/ecashaddrjs" + ecashaddrjs "file:../.cache/yarn/v6/npm-chronik-client-cashtokens-3.1.1-rc0-e5e4a3538e8010b70623974a731bf2712506c5e3-integrity/node_modules/ecashaddrjs" isomorphic-ws "^4.0.1" protobufjs "^6.8.8" ws "^8.3.0" @@ -2830,7 +2830,7 @@ ecashaddrjs@^1.0.7: big-integer "1.6.36" bs58check "^3.0.1" -ecashaddrjs@^2.0.0, "ecashaddrjs@file:../.cache/yarn/v6/npm-chronik-client-cashtokens-3.1.1-rc0-e5e4a3538e8010b70623974a731bf2712506c5e3-integrity/node_modules/ecashaddrjs": +ecashaddrjs@^2.0.0, "ecashaddrjs@file:../../../.cache/yarn/v6/npm-chronik-client-cashtokens-3.1.1-rc0-e5e4a3538e8010b70623974a731bf2712506c5e3-integrity/node_modules/ecashaddrjs": version "2.0.0" resolved "https://registry.yarnpkg.com/ecashaddrjs/-/ecashaddrjs-2.0.0.tgz#d45ede7fb6168815dbcf664b8e0a6872e485d874" integrity sha512-EvK1V4D3+nIEoD0ggy/b0F4lW39/72R9aOs/scm6kxMVuXu16btc+H74eQv7okNfXaQWKgolEekZkQ6wfcMMLw==