/** * Component: AddressNFTTransactions * Author: Nearblocks Pte Ltd * License: Business Source License 1.1 * Description: NFT Transactions of address on Near Protocol. * @interface Props * @param {string} [network] - The network data to show, either mainnet or testnet * @param {Function} [t] - A function for internationalization (i18n) provided by the next-translate package. * @param {string} [id] - The account identifier passed as a string * @param {Object.<string, string>} [filters] - Key-value pairs for filtering transactions. (Optional) * Example: If provided, method=batch will filter the blocks with method=batch. * @param {function} [handleFilter] - Function to handle filter changes. (Optional) * Example: handleFilter={handlePageFilter} where handlePageFilter is a function to filter the page. * @param {function} [onFilterClear] - Function to clear a specific or all filters. (Optional) * Example: onFilterClear={handleClearFilter} where handleClearFilter is a function to clear the applied filters. */ /* INCLUDE: "includes/formats.jsx" */ function localFormat(number) { const bigNumber = Big(number); const formattedNumber = bigNumber .toFixed(5) .replace(/(\d)(?=(\d{3})+\.)/g, '$1,'); // Add commas before the decimal point return formattedNumber.replace(/\.?0*$/, ''); // Remove trailing zeros and the dot } function dollarFormat(number) { const bigNumber = new Big(number); // Format to two decimal places without thousands separator const formattedNumber = bigNumber.toFixed(2); // Add comma as a thousands separator const parts = formattedNumber.split('.'); parts[0] = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ','); const dollarFormattedNumber = `${parts.join('.')}`; return dollarFormattedNumber; } function dollarNonCentFormat(number) { const bigNumber = new Big(number).toFixed(0); // Extract integer part and format with commas const integerPart = bigNumber.toString(); const formattedInteger = integerPart.replace(/\B(?=(\d{3})+(?!\d))/g, ','); return formattedInteger; } function weight(number) { let sizeInBytes = new Big(number); if (sizeInBytes.lt(0)) { throw new Error('Invalid input. Please provide a non-negative number.'); } const suffixes = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']; let suffixIndex = 0; while (sizeInBytes.gte(1000) && suffixIndex < suffixes.length - 1) { sizeInBytes = sizeInBytes.div(1000); // Assign the result back to sizeInBytes suffixIndex++; } const formattedSize = sizeInBytes.toFixed(2) + ' ' + suffixes[suffixIndex]; return formattedSize; } function convertToUTC(timestamp, hour) { const date = new Date(timestamp); // Get UTC date components const utcYear = date.getUTCFullYear(); const utcMonth = ('0' + (date.getUTCMonth() + 1)).slice(-2); // Adding 1 because months are zero-based const utcDay = ('0' + date.getUTCDate()).slice(-2); const utcHours = ('0' + date.getUTCHours()).slice(-2); const utcMinutes = ('0' + date.getUTCMinutes()).slice(-2); const utcSeconds = ('0' + date.getUTCSeconds()).slice(-2); // Array of month abbreviations const monthAbbreviations = [ 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec', ]; const monthIndex = Number(utcMonth) - 1; // Format the date as required (Jul-25-2022 16:25:37) let formattedDate = monthAbbreviations[monthIndex] + '-' + utcDay + '-' + utcYear + ' ' + utcHours + ':' + utcMinutes + ':' + utcSeconds; if (hour) { // Convert hours to 12-hour format let hour12 = parseInt(utcHours); const ampm = hour12 >= 12 ? 'PM' : 'AM'; hour12 = hour12 % 12 || 12; // Add AM/PM to the formatted date (Jul-25-2022 4:25:37 PM) formattedDate = monthAbbreviations[monthIndex] + '-' + utcDay + '-' + utcYear + ' ' + hour12 + ':' + utcMinutes + ':' + utcSeconds + ' ' + ampm; } return formattedDate; } function getTimeAgoString(timestamp) { const currentUTC = Date.now(); const date = new Date(timestamp); const seconds = Math.floor((currentUTC - date.getTime()) / 1000); const intervals = { year: seconds / (60 * 60 * 24 * 365), month: seconds / (60 * 60 * 24 * 30), week: seconds / (60 * 60 * 24 * 7), day: seconds / (60 * 60 * 24), hour: seconds / (60 * 60), minute: seconds / 60, }; if (intervals.year >= 1) { return ( Math.floor(intervals.year) + ' year' + (Math.floor(intervals.year) > 1 ? 's' : '') + ' ago' ); } else if (intervals.month >= 1) { return ( Math.floor(intervals.month) + ' month' + (Math.floor(intervals.month) > 1 ? 's' : '') + ' ago' ); } else if (intervals.day >= 1) { return ( Math.floor(intervals.day) + ' day' + (Math.floor(intervals.day) > 1 ? 's' : '') + ' ago' ); } else if (intervals.hour >= 1) { return ( Math.floor(intervals.hour) + ' hour' + (Math.floor(intervals.hour) > 1 ? 's' : '') + ' ago' ); } else if (intervals.minute >= 1) { return ( Math.floor(intervals.minute) + ' minute' + (Math.floor(intervals.minute) > 1 ? 's' : '') + ' ago' ); } else { return 'a few seconds ago'; } } function formatWithCommas(number) { return number.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ','); } function formatTimestampToString(timestamp) { const date = new Date(timestamp); // Format the date to 'YYYY-MM-DD HH:mm:ss' format const formattedDate = date.toISOString().replace('T', ' ').split('.')[0]; return formattedDate; } function convertToMetricPrefix(numberStr) { const prefixes = ['', 'k', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y']; // Metric prefixes let result = new Big(numberStr); let count = 0; while (result.abs().gte('1e3') && count < prefixes.length - 1) { result = result.div(1e3); count++; } // Check if the value is an integer or has more than two digits before the decimal point if (result.abs().lt(1e2) && result.toFixed(2) !== result.toFixed(0)) { result = result.toFixed(2); } else { result = result.toFixed(0); } return result.toString() + ' ' + prefixes[count]; } function formatNumber(value) { let bigValue = new Big(value); const suffixes = ['', 'K', 'M', 'B', 'T']; let suffixIndex = 0; while (bigValue.gte(10000) && suffixIndex < suffixes.length - 1) { bigValue = bigValue.div(1000); suffixIndex++; } const formattedValue = bigValue.toFixed(1).replace(/\.0+$/, ''); return `${formattedValue} ${suffixes[suffixIndex]}`; } function gasFee(gas, price) { const near = yoctoToNear(Big(gas).mul(Big(price)).toString(), true); return `${near}`; } function currency(number) { let absNumber = new Big(number).abs(); const suffixes = ['', 'K', 'M', 'B', 'T', 'Q']; let suffixIndex = 0; while (absNumber.gte(1000) && suffixIndex < suffixes.length - 1) { absNumber = absNumber.div(1000); // Divide using big.js's div method suffixIndex++; } const formattedNumber = absNumber.toFixed(2); // Format with 2 decimal places return ( (number < '0' ? '-' : '') + formattedNumber + ' ' + suffixes[suffixIndex] ); } function formatDate(dateString) { const inputDate = new Date(dateString); const days = [ 'Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', ]; const months = [ 'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December', ]; const dayOfWeek = days[inputDate.getDay()]; const month = months[inputDate.getMonth()]; const day = inputDate.getDate(); const year = inputDate.getFullYear(); const formattedDate = dayOfWeek + ', ' + month + ' ' + day + ', ' + year; return formattedDate; } function formatCustomDate(inputDate) { var date = new Date(inputDate); // Array of month names var monthNames = [ 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec', ]; // Get month and day var month = monthNames[date.getMonth()]; var day = date.getDate(); // Create formatted date string in "MMM DD" format var formattedDate = month + ' ' + (day < 10 ? '0' + day : day); return formattedDate; } function shortenHex(address) { return `${address && address.substr(0, 6)}...${address.substr(-4)}`; } function capitalizeFirstLetter(string) { return string.charAt(0).toUpperCase() + string.slice(1); } function shortenToken(token) { return truncateString(token, 14, ''); } function shortenTokenSymbol(token) { return truncateString(token, 5, ''); } function gasPercentage(gasUsed, gasAttached) { if (!gasAttached) return 'N/A'; const formattedNumber = (Big(gasUsed).div(Big(gasAttached)) * 100).toFixed(2); return `${formattedNumber}%`; } function serialNumber(index, page, perPage) { return index + 1 + (page - 1) * perPage; } function capitalizeWords(str) { const words = str.split('_'); const capitalizedWords = words.map( (word) => word.charAt(0).toUpperCase() + word.slice(1), ); const result = capitalizedWords.join(' '); return result; } function truncateString(str, maxLength, suffix) { if (str.length <= maxLength) { return str; } return str.substring(0, maxLength) + suffix; } function yoctoToNear(yocto, format) { const YOCTO_PER_NEAR = Big(10).pow(24).toString(); const near = Big(yocto).div(YOCTO_PER_NEAR).toString(); return format ? localFormat(near) : near; } function truncateString(str, maxLength, suffix) { if (str.length <= maxLength) { return str; } return str.substring(0, maxLength) + suffix; } function yoctoToNear(yocto, format) { const YOCTO_PER_NEAR = Big(10).pow(24).toString(); const near = Big(yocto).div(YOCTO_PER_NEAR).toString(); return format ? localFormat(near) : near; } function truncateString(str, maxLength, suffix) { if (str.length <= maxLength) { return str; } return str.substring(0, maxLength) + suffix; } function yoctoToNear(yocto, format) { const YOCTO_PER_NEAR = Big(10).pow(24).toString(); const near = Big(yocto).div(YOCTO_PER_NEAR).toString(); return format ? localFormat(near) : near; } function getTimeAgoString(timestamp) { const currentUTC = Date.now(); const date = new Date(timestamp); const seconds = Math.floor((currentUTC - date.getTime()) / 1000); const intervals = { year: seconds / (60 * 60 * 24 * 365), month: seconds / (60 * 60 * 24 * 30), week: seconds / (60 * 60 * 24 * 7), day: seconds / (60 * 60 * 24), hour: seconds / (60 * 60), minute: seconds / 60, }; if (intervals.year >= 1) { return ( Math.floor(intervals.year) + ' year' + (Math.floor(intervals.year) > 1 ? 's' : '') + ' ago' ); } else if (intervals.month >= 1) { return ( Math.floor(intervals.month) + ' month' + (Math.floor(intervals.month) > 1 ? 's' : '') + ' ago' ); } else if (intervals.day >= 1) { return ( Math.floor(intervals.day) + ' day' + (Math.floor(intervals.day) > 1 ? 's' : '') + ' ago' ); } else if (intervals.hour >= 1) { return ( Math.floor(intervals.hour) + ' hour' + (Math.floor(intervals.hour) > 1 ? 's' : '') + ' ago' ); } else if (intervals.minute >= 1) { return ( Math.floor(intervals.minute) + ' minute' + (Math.floor(intervals.minute) > 1 ? 's' : '') + ' ago' ); } else { return 'a few seconds ago'; } } function formatWithCommas(number) { return number.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ','); } function formatTimestampToString(timestamp) { const date = new Date(timestamp); // Format the date to 'YYYY-MM-DD HH:mm:ss' format const formattedDate = date.toISOString().replace('T', ' ').split('.')[0]; return formattedDate; } function convertToMetricPrefix(numberStr) { const prefixes = ['', 'k', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y']; // Metric prefixes let result = new Big(numberStr); let count = 0; while (result.abs().gte('1e3') && count < prefixes.length - 1) { result = result.div(1e3); count++; } // Check if the value is an integer or has more than two digits before the decimal point if (result.abs().lt(1e2) && result.toFixed(2) !== result.toFixed(0)) { result = result.toFixed(2); } else { result = result.toFixed(0); } return result.toString() + ' ' + prefixes[count]; } function formatNumber(value) { let bigValue = new Big(value); const suffixes = ['', 'K', 'M', 'B', 'T']; let suffixIndex = 0; while (bigValue.gte(10000) && suffixIndex < suffixes.length - 1) { bigValue = bigValue.div(1000); suffixIndex++; } const formattedValue = bigValue.toFixed(1).replace(/\.0+$/, ''); return `${formattedValue} ${suffixes[suffixIndex]}`; } function gasFee(gas, price) { const near = yoctoToNear(Big(gas).mul(Big(price)).toString(), true); return `${near}`; } function currency(number) { let absNumber = new Big(number).abs(); const suffixes = ['', 'K', 'M', 'B', 'T', 'Q']; let suffixIndex = 0; while (absNumber.gte(1000) && suffixIndex < suffixes.length - 1) { absNumber = absNumber.div(1000); // Divide using big.js's div method suffixIndex++; } const formattedNumber = absNumber.toFixed(2); // Format with 2 decimal places return ( (number < '0' ? '-' : '') + formattedNumber + ' ' + suffixes[suffixIndex] ); } function formatDate(dateString) { const inputDate = new Date(dateString); const days = [ 'Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', ]; const months = [ 'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December', ]; const dayOfWeek = days[inputDate.getDay()]; const month = months[inputDate.getMonth()]; const day = inputDate.getDate(); const year = inputDate.getFullYear(); const formattedDate = dayOfWeek + ', ' + month + ' ' + day + ', ' + year; return formattedDate; } function formatCustomDate(inputDate) { var date = new Date(inputDate); // Array of month names var monthNames = [ 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec', ]; // Get month and day var month = monthNames[date.getMonth()]; var day = date.getDate(); // Create formatted date string in "MMM DD" format var formattedDate = month + ' ' + (day < 10 ? '0' + day : day); return formattedDate; } function shortenHex(address) { return `${address && address.substr(0, 6)}...${address.substr(-4)}`; } function capitalizeFirstLetter(string) { return string.charAt(0).toUpperCase() + string.slice(1); } function shortenToken(token) { return truncateString(token, 14, ''); } function shortenTokenSymbol(token) { return truncateString(token, 5, ''); } function gasPercentage(gasUsed, gasAttached) { if (!gasAttached) return 'N/A'; const formattedNumber = (Big(gasUsed).div(Big(gasAttached)) * 100).toFixed(2); return `${formattedNumber}%`; } function serialNumber(index, page, perPage) { return index + 1 + (page - 1) * perPage; } function capitalizeWords(str) { const words = str.split('_'); const capitalizedWords = words.map( (word) => word.charAt(0).toUpperCase() + word.slice(1), ); const result = capitalizedWords.join(' '); return result; } function truncateString(str, maxLength, suffix) { if (str.length <= maxLength) { return str; } return str.substring(0, maxLength) + suffix; } function yoctoToNear(yocto, format) { const YOCTO_PER_NEAR = Big(10).pow(24).toString(); const near = Big(yocto).div(YOCTO_PER_NEAR).toString(); return format ? localFormat(near) : near; } function truncateString(str, maxLength, suffix) { if (str.length <= maxLength) { return str; } return str.substring(0, maxLength) + suffix; } function yoctoToNear(yocto, format) { const YOCTO_PER_NEAR = Big(10).pow(24).toString(); const near = Big(yocto).div(YOCTO_PER_NEAR).toString(); return format ? localFormat(near) : near; } function truncateString(str, maxLength, suffix) { if (str.length <= maxLength) { return str; } return str.substring(0, maxLength) + suffix; } function yoctoToNear(yocto, format) { const YOCTO_PER_NEAR = Big(10).pow(24).toString(); const near = Big(yocto).div(YOCTO_PER_NEAR).toString(); return format ? localFormat(near) : near; } function formatTimestampToString(timestamp) { const date = new Date(timestamp); // Format the date to 'YYYY-MM-DD HH:mm:ss' format const formattedDate = date.toISOString().replace('T', ' ').split('.')[0]; return formattedDate; } function convertToMetricPrefix(numberStr) { const prefixes = ['', 'k', 'M', 'G', 'T', 'P', 'E', 'Z', 'Y']; // Metric prefixes let result = new Big(numberStr); let count = 0; while (result.abs().gte('1e3') && count < prefixes.length - 1) { result = result.div(1e3); count++; } // Check if the value is an integer or has more than two digits before the decimal point if (result.abs().lt(1e2) && result.toFixed(2) !== result.toFixed(0)) { result = result.toFixed(2); } else { result = result.toFixed(0); } return result.toString() + ' ' + prefixes[count]; } function formatNumber(value) { let bigValue = new Big(value); const suffixes = ['', 'K', 'M', 'B', 'T']; let suffixIndex = 0; while (bigValue.gte(10000) && suffixIndex < suffixes.length - 1) { bigValue = bigValue.div(1000); suffixIndex++; } const formattedValue = bigValue.toFixed(1).replace(/\.0+$/, ''); return `${formattedValue} ${suffixes[suffixIndex]}`; } function gasFee(gas, price) { const near = yoctoToNear(Big(gas).mul(Big(price)).toString(), true); return `${near}`; } function currency(number) { let absNumber = new Big(number).abs(); const suffixes = ['', 'K', 'M', 'B', 'T', 'Q']; let suffixIndex = 0; while (absNumber.gte(1000) && suffixIndex < suffixes.length - 1) { absNumber = absNumber.div(1000); // Divide using big.js's div method suffixIndex++; } const formattedNumber = absNumber.toFixed(2); // Format with 2 decimal places return ( (number < '0' ? '-' : '') + formattedNumber + ' ' + suffixes[suffixIndex] ); } function formatDate(dateString) { const inputDate = new Date(dateString); const days = [ 'Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', ]; const months = [ 'January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December', ]; const dayOfWeek = days[inputDate.getDay()]; const month = months[inputDate.getMonth()]; const day = inputDate.getDate(); const year = inputDate.getFullYear(); const formattedDate = dayOfWeek + ', ' + month + ' ' + day + ', ' + year; return formattedDate; } function formatCustomDate(inputDate) { var date = new Date(inputDate); // Array of month names var monthNames = [ 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec', ]; // Get month and day var month = monthNames[date.getMonth()]; var day = date.getDate(); // Create formatted date string in "MMM DD" format var formattedDate = month + ' ' + (day < 10 ? '0' + day : day); return formattedDate; } function shortenHex(address) { return `${address && address.substr(0, 6)}...${address.substr(-4)}`; } function capitalizeFirstLetter(string) { return string.charAt(0).toUpperCase() + string.slice(1); } function shortenToken(token) { return truncateString(token, 14, ''); } function shortenTokenSymbol(token) { return truncateString(token, 5, ''); } function gasPercentage(gasUsed, gasAttached) { if (!gasAttached) return 'N/A'; const formattedNumber = (Big(gasUsed).div(Big(gasAttached)) * 100).toFixed(2); return `${formattedNumber}%`; } function serialNumber(index, page, perPage) { return index + 1 + (page - 1) * perPage; } function capitalizeWords(str) { const words = str.split('_'); const capitalizedWords = words.map( (word) => word.charAt(0).toUpperCase() + word.slice(1), ); const result = capitalizedWords.join(' '); return result; } function truncateString(str, maxLength, suffix) { if (str.length <= maxLength) { return str; } return str.substring(0, maxLength) + suffix; } function yoctoToNear(yocto, format) { const YOCTO_PER_NEAR = Big(10).pow(24).toString(); const near = Big(yocto).div(YOCTO_PER_NEAR).toString(); return format ? localFormat(near) : near; } function truncateString(str, maxLength, suffix) { if (str.length <= maxLength) { return str; } return str.substring(0, maxLength) + suffix; } function yoctoToNear(yocto, format) { const YOCTO_PER_NEAR = Big(10).pow(24).toString(); const near = Big(yocto).div(YOCTO_PER_NEAR).toString(); return format ? localFormat(near) : near; } function truncateString(str, maxLength, suffix) { if (str.length <= maxLength) { return str; } return str.substring(0, maxLength) + suffix; } function yoctoToNear(yocto, format) { const YOCTO_PER_NEAR = Big(10).pow(24).toString(); const near = Big(yocto).div(YOCTO_PER_NEAR).toString(); return format ? localFormat(near) : near; } function capitalizeFirstLetter(string) { return string.charAt(0).toUpperCase() + string.slice(1); } function shortenToken(token) { return truncateString(token, 14, ''); } function shortenTokenSymbol(token) { return truncateString(token, 5, ''); } function gasPercentage(gasUsed, gasAttached) { if (!gasAttached) return 'N/A'; const formattedNumber = (Big(gasUsed).div(Big(gasAttached)) * 100).toFixed(2); return `${formattedNumber}%`; } function serialNumber(index, page, perPage) { return index + 1 + (page - 1) * perPage; } function capitalizeWords(str) { const words = str.split('_'); const capitalizedWords = words.map( (word) => word.charAt(0).toUpperCase() + word.slice(1), ); const result = capitalizedWords.join(' '); return result; } function truncateString(str, maxLength, suffix) { if (str.length <= maxLength) { return str; } return str.substring(0, maxLength) + suffix; } function yoctoToNear(yocto, format) { const YOCTO_PER_NEAR = Big(10).pow(24).toString(); const near = Big(yocto).div(YOCTO_PER_NEAR).toString(); return format ? localFormat(near) : near; } function truncateString(str, maxLength, suffix) { if (str.length <= maxLength) { return str; } return str.substring(0, maxLength) + suffix; } function yoctoToNear(yocto, format) { const YOCTO_PER_NEAR = Big(10).pow(24).toString(); const near = Big(yocto).div(YOCTO_PER_NEAR).toString(); return format ? localFormat(near) : near; } /* END_INCLUDE: "includes/formats.jsx" */ /* INCLUDE: "includes/libs.jsx" */ function getConfig(network) { switch (network) { case 'mainnet': return { ownerId: 'nearblocks.near', nodeUrl: 'https://rpc.mainnet.near.org', backendUrl: 'https://api3.nearblocks.io/v1/', rpcUrl: 'https://archival-rpc.testnet.near.org', appUrl: 'https://nearblocks.io/', }; case 'testnet': return { ownerId: 'nearblocks.testnet', nodeUrl: 'https://rpc.testnet.near.org', backendUrl: 'https://api3-testnet.nearblocks.io/v1/', rpcUrl: 'https://archival-rpc.testnet.near.org', appUrl: 'https://testnet.nearblocks.io/', }; default: return {}; } } function debounce( delay, func, ) { let timer; let active = true; const debounced = (arg) => { if (active) { clearTimeout(timer); timer = setTimeout(() => { active && func(arg); timer = undefined; }, delay); } else { func(arg); } }; debounced.isPending = () => { return timer !== undefined; }; debounced.cancel = () => { active = false; }; debounced.flush = (arg) => func(arg); return debounced; } function timeAgo(unixTimestamp) { const currentTimestamp = Math.floor(Date.now() / 1000); const secondsAgo = currentTimestamp - unixTimestamp; if (secondsAgo < 5) { return 'Just now'; } else if (secondsAgo < 60) { return `${secondsAgo} seconds ago`; } else if (secondsAgo < 3600) { const minutesAgo = Math.floor(secondsAgo / 60); return `${minutesAgo} minute${minutesAgo > 1 ? 's' : ''} ago`; } else if (secondsAgo < 86400) { const hoursAgo = Math.floor(secondsAgo / 3600); return `${hoursAgo} hour${hoursAgo > 1 ? 's' : ''} ago`; } else { const daysAgo = Math.floor(secondsAgo / 86400); return `${daysAgo} day${daysAgo > 1 ? 's' : ''} ago`; } } function shortenAddress(address) { const string = String(address); if (string.length <= 20) return string; return `${string.substr(0, 10)}...${string.substr(-7)}`; } function urlHostName(url) { try { const domain = new URL(url); return domain?.hostname ?? null; } catch (e) { return null; } } function holderPercentage(supply, quantity) { return Math.min(Big(quantity).div(Big(supply)).mul(Big(100)).toFixed(2), 100); } function isAction(type) { const actions = [ 'DEPLOY_CONTRACT', 'TRANSFER', 'STAKE', 'ADD_KEY', 'DELETE_KEY', 'DELETE_ACCOUNT', ]; return actions.includes(type.toUpperCase()); } function localFormat(number) { const bigNumber = Big(number); const formattedNumber = bigNumber .toFixed(5) .replace(/(\d)(?=(\d{3})+\.)/g, '$1,'); // Add commas before the decimal point return formattedNumber.replace(/\.?0*$/, ''); // Remove trailing zeros and the dot } function formatWithCommas(number) { return number.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ','); } function nanoToMilli(nano) { return Big(nano).div(Big(10).pow(6)).round().toNumber(); } function truncateString(str, maxLength, suffix) { if (str.length <= maxLength) { return str; } return str.substring(0, maxLength) + suffix; } function getConfig(network) { switch (network) { case 'mainnet': return { ownerId: 'nearblocks.near', nodeUrl: 'https://rpc.mainnet.near.org', backendUrl: 'https://api3.nearblocks.io/v1/', rpcUrl: 'https://archival-rpc.testnet.near.org', appUrl: 'https://nearblocks.io/', }; case 'testnet': return { ownerId: 'nearblocks.testnet', nodeUrl: 'https://rpc.testnet.near.org', backendUrl: 'https://api3-testnet.nearblocks.io/v1/', rpcUrl: 'https://archival-rpc.testnet.near.org', appUrl: 'https://testnet.nearblocks.io/', }; default: return {}; } } function debounce( delay, func, ) { let timer; let active = true; const debounced = (arg) => { if (active) { clearTimeout(timer); timer = setTimeout(() => { active && func(arg); timer = undefined; }, delay); } else { func(arg); } }; debounced.isPending = () => { return timer !== undefined; }; debounced.cancel = () => { active = false; }; debounced.flush = (arg) => func(arg); return debounced; } function timeAgo(unixTimestamp) { const currentTimestamp = Math.floor(Date.now() / 1000); const secondsAgo = currentTimestamp - unixTimestamp; if (secondsAgo < 5) { return 'Just now'; } else if (secondsAgo < 60) { return `${secondsAgo} seconds ago`; } else if (secondsAgo < 3600) { const minutesAgo = Math.floor(secondsAgo / 60); return `${minutesAgo} minute${minutesAgo > 1 ? 's' : ''} ago`; } else if (secondsAgo < 86400) { const hoursAgo = Math.floor(secondsAgo / 3600); return `${hoursAgo} hour${hoursAgo > 1 ? 's' : ''} ago`; } else { const daysAgo = Math.floor(secondsAgo / 86400); return `${daysAgo} day${daysAgo > 1 ? 's' : ''} ago`; } } function shortenAddress(address) { const string = String(address); if (string.length <= 20) return string; return `${string.substr(0, 10)}...${string.substr(-7)}`; } function urlHostName(url) { try { const domain = new URL(url); return domain?.hostname ?? null; } catch (e) { return null; } } function holderPercentage(supply, quantity) { return Math.min(Big(quantity).div(Big(supply)).mul(Big(100)).toFixed(2), 100); } function isAction(type) { const actions = [ 'DEPLOY_CONTRACT', 'TRANSFER', 'STAKE', 'ADD_KEY', 'DELETE_KEY', 'DELETE_ACCOUNT', ]; return actions.includes(type.toUpperCase()); } function localFormat(number) { const bigNumber = Big(number); const formattedNumber = bigNumber .toFixed(5) .replace(/(\d)(?=(\d{3})+\.)/g, '$1,'); // Add commas before the decimal point return formattedNumber.replace(/\.?0*$/, ''); // Remove trailing zeros and the dot } function formatWithCommas(number) { return number.toString().replace(/\B(?=(\d{3})+(?!\d))/g, ','); } /* END_INCLUDE: "includes/libs.jsx" */ /* INCLUDE COMPONENT: "includes/Common/Status.jsx" */ const FaCheckCircle = () => { return ( <svg xmlns="http://www.w3.org/2000/svg" height="1em" viewBox="0 0 512 512"> <path d="M504 256c0 136.967-111.033 248-248 248S8 392.967 8 256 119.033 8 256 8s248 111.033 248 248zM227.314 387.314l184-184c6.248-6.248 6.248-16.379 0-22.627l-22.627-22.627c-6.248-6.249-16.379-6.249-22.628 0L216 308.118l-70.059-70.059c-6.248-6.248-16.379-6.248-22.628 0l-22.627 22.627c-6.248 6.248-6.248 16.379 0 22.627l104 104c6.249 6.249 16.379 6.249 22.628.001z" fill="#50C878" /> </svg> ); }; const FaTimesCircle = () => { return ( <svg xmlns="http://www.w3.org/2000/svg" height="1em" viewBox="0 0 512 512"> <path d="M256 8C119 8 8 119 8 256s111 248 248 248 248-111 248-248S393 8 256 8zm121.6 313.1c4.7 4.7 4.7 12.3 0 17L338 377.6c-4.7 4.7-12.3 4.7-17 0L256 312l-65.1 65.6c-4.7 4.7-12.3 4.7-17 0L134.4 338c-4.7-4.7-4.7-12.3 0-17l65.6-65-65.6-65.1c-4.7-4.7-4.7-12.3 0-17l39.6-39.6c4.7-4.7 12.3-4.7 17 0l65 65.7 65.1-65.6c4.7-4.7 12.3-4.7 17 0l39.6 39.6c4.7 4.7 4.7 12.3 0 17L312 256l65.6 65.1z" fill="#ff0000" /> </svg> ); }; const FaHourglassStart = () => { return ( <svg xmlns="http://www.w3.org/2000/svg" height="1em" viewBox="0 0 384 512"> <path d="M32 0C14.3 0 0 14.3 0 32S14.3 64 32 64V75c0 42.4 16.9 83.1 46.9 113.1L146.7 256 78.9 323.9C48.9 353.9 32 394.6 32 437v11c-17.7 0-32 14.3-32 32s14.3 32 32 32H64 320h32c17.7 0 32-14.3 32-32s-14.3-32-32-32V437c0-42.4-16.9-83.1-46.9-113.1L237.3 256l67.9-67.9c30-30 46.9-70.7 46.9-113.1V64c17.7 0 32-14.3 32-32s-14.3-32-32-32H320 64 32zM288 437v11H96V437c0-25.5 10.1-49.9 28.1-67.9L192 301.3l67.9 67.9c18 18 28.1 42.4 28.1 67.9z" fill="#FFEB3B" /> </svg> ); }; const getOptions = (status) => { switch (status) { case null: return { bg: 'bg-yellow-50', text: 'text-yellow-500', icon: FaHourglassStart, label: 'Pending', }; case false: return { bg: 'bg-red-50', text: 'text-red-500', icon: FaTimesCircle, label: 'Failure', }; default: return { bg: 'bg-emerald-50', text: 'text-emerald-500', icon: FaCheckCircle, label: 'Success', }; } }; const TxnStatus = (props) => { const option = getOptions(props.status); const Icon = option.icon; return ( <div className="w-full md:w-3/4 break-words"> <span className={`inline-flex items-center text-xs rounded py-1 ${ option.bg } ${option.text} ${props.showLabel ? ' px-2' : ' px-1'}`} > <Icon /> {props.showLabel && <span className="ml-2">{option.label}</span>} </span> </div> ); };/* END_INCLUDE COMPONENT: "includes/Common/Status.jsx" */ /* INCLUDE COMPONENT: "includes/Common/Filter.jsx" */ const Filter = (props) => { return ( <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width={16} height={16} {...props} > <path fill="none" d="M0 0h24v24H0z" /> <path d="M14 14v6l-4 2v-8L4 5V3h16v2l-6 9zM6.404 5L12 13.394 17.596 5H6.404z" /> </svg> ); };/* END_INCLUDE COMPONENT: "includes/Common/Filter.jsx" */ /* INCLUDE COMPONENT: "includes/icons/SortIcon.jsx" */ const ArrowUp = (props) => { return ( <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width={24} height={24} {...props} > <path fill="none" d="M0 0h24v24H0z" /> <path d="M12 10.828l-4.95 4.95-1.414-1.414L12 8l6.364 6.364-1.414 1.414z" /> </svg> ); }; const SortIcon = (props) => { return ( <ArrowUp className={`h-3 w-3 fill-current transition-transform mr-1 duration-700 ${ props.order !== 'asc' ? 'transform rotate-180' : 'transform rotate-0' }`} /> ); };/* END_INCLUDE COMPONENT: "includes/icons/SortIcon.jsx" */ /* INCLUDE COMPONENT: "includes/icons/CloseCircle.jsx" */ const CloseCircle = (props) => { const handleClick = () => { if (props.onClick) { props.onClick('All'); } }; return ( <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" width={24} height={24} className={props.className} onClick={handleClick} > <path fill="none" d="M0 0h24v24H0z" /> <path d="M12 22C6.477 22 2 17.523 2 12S6.477 2 12 2s10 4.477 10 10-4.477 10-10 10zm0-2a8 8 0 100-16 8 8 0 000 16zm0-9.414l2.828-2.829 1.415 1.415L13.414 12l2.829 2.828-1.415 1.415L12 13.414l-2.828 2.829-1.415-1.415L10.586 12 7.757 9.172l1.415-1.415L12 10.586z" /> </svg> ); };/* END_INCLUDE COMPONENT: "includes/icons/CloseCircle.jsx" */ /* INCLUDE COMPONENT: "includes/Common/Skeleton.jsx" */ /** * @interface Props * @param {string} [className] - The CSS class name(s) for styling purposes. */ const Skeleton = (props) => { return ( <div className={`bg-gray-200 rounded shadow-sm animate-pulse ${props.className}`} ></div> ); };/* END_INCLUDE COMPONENT: "includes/Common/Skeleton.jsx" */ /* INCLUDE COMPONENT: "includes/icons/Clock.jsx" */ /** * @interface Props * @param {string} [className] - The CSS class name(s) for styling purposes. */ const Clock = (props) => ( <svg viewBox="64 64 896 896" focusable="false" data-icon="clock-circle" width="1em" height="1em" fill="currentColor" aria-hidden="true" {...props} > <path d="M512 64C264.6 64 64 264.6 64 512s200.6 448 448 448 448-200.6 448-448S759.4 64 512 64zm0 820c-205.4 0-372-166.6-372-372s166.6-372 372-372 372 166.6 372 372-166.6 372-372 372z"></path> <path d="M686.7 638.6L544.1 535.5V288c0-4.4-3.6-8-8-8H488c-4.4 0-8 3.6-8 8v275.4c0 2.6 1.2 5 3.3 6.5l165.4 120.6c3.6 2.6 8.6 1.8 11.2-1.7l28.6-39c2.6-3.7 1.8-8.7-1.8-11.2z"></path> </svg> );/* END_INCLUDE COMPONENT: "includes/icons/Clock.jsx" */ /* INCLUDE COMPONENT: "includes/icons/TokenImage.jsx" */ /** * @interface Props * @param {string} [src] - The URL string pointing to the image source. * @param {string} [alt] - The alternate text description for the image. * @param {string} [className] - The CSS class name(s) for styling purposes. * @param {string} [appUrl] - The URL of the application. */ const TokenImage = ({ appUrl, src, alt, className, onLoad, onSetSrc, }) => { const placeholder = `${appUrl}images/tokenplaceholder.svg`; const handleLoad = () => { if (onLoad) { onLoad(); } }; const handleError = () => { if (onSetSrc) { onSetSrc(placeholder); } if (onLoad) { onLoad(); } }; return ( <img src={src || placeholder} alt={alt} className={className} onLoad={handleLoad} onError={handleError} /> ); };/* END_INCLUDE COMPONENT: "includes/icons/TokenImage.jsx" */ /* INCLUDE COMPONENT: "includes/icons/Download.jsx" */ const Download = () => { return ( <svg width="11" height="12" viewBox="0 0 11 12" fill="none" xmlns="http://www.w3.org/2000/svg" > <path fillRule="evenodd" clipRule="evenodd" d="M10.5418 12V2H6.87516V3H9.62516V11H1.37516V3H4.12516V2H0.458496V12H10.5418ZM5.04183 5.5H3.2085L5.50016 8.5L7.79183 5.5H5.9585V0H5.04183V5.5Z" fill="#4b5563" /> </svg> ); };/* END_INCLUDE COMPONENT: "includes/icons/Download.jsx" */ function MainComponent(props) { const { network, t, id, filters, handleFilter, onFilterClear } = props; const [isLoading, setIsLoading] = useState(false); const [totalCount, setTotalCount] = useState(0); const [txns, setTxns] = useState({}); const [showAge, setShowAge] = useState(true); const [currentPage, setCurrentPage] = useState(1); const [sorting, setSorting] = useState('desc'); const errorMessage = t ? t('txns:noTxns') : ' No transactions found!'; const config = getConfig(network); const toggleShowAge = () => setShowAge((s) => !s); const setPage = (pageNumber) => { setCurrentPage(pageNumber); }; useEffect(() => { function fetchTotalTxns(qs) { const queryParams = qs ? '?' + qs : ''; asyncFetch( `${config?.backendUrl}account/${id}/nft-txns/count?${queryParams}`, { method: 'GET', headers: { 'Content-Type': 'application/json', }, }, ) .then( (data ) => { const resp = data?.body?.txns?.[0]; if (data.status === 200) { setTotalCount(resp?.count); } }, ) .catch(() => {}) .finally(() => {}); } function fetchTxnsData(qs, sqs, page) { setIsLoading(true); const queryParams = qs ? qs + '&' : ''; asyncFetch( `${config?.backendUrl}account/${id}/nft-txns?${queryParams}order=${sqs}&page=${page}&per_page=25`, { method: 'GET', headers: { 'Content-Type': 'application/json', }, }, ) .then((data) => { const resp = data?.body?.txns; if (data.status === 200) { if (Array.isArray(resp) && resp.length > 0) { setTxns((prevData) => ({ ...prevData, [page]: resp || [] })); } else if (resp.length === 0) { setTxns({}); } } }) .catch(() => {}) .finally(() => { setIsLoading(false); }); } let urlString = ''; if (filters && Object.keys(filters).length > 0) { urlString = Object.keys(filters) .map( (key) => `${encodeURIComponent(key)}=${encodeURIComponent(filters[key])}`, ) .join('&'); } if (urlString && sorting) { fetchTotalTxns(urlString); fetchTxnsData(urlString, sorting, currentPage); } else if (sorting && (!filters || Object.keys(filters).length === 0)) { fetchTotalTxns(); fetchTxnsData('', sorting, currentPage); } }, [config?.backendUrl, id, currentPage, filters, sorting]); let filterValue; const onInputChange = (event) => { filterValue = event.target.value; }; const onFilter = ( e, name, ) => { e.preventDefault(); handleFilter(name, filterValue); }; const onClear = (name) => { if (onFilterClear && filters) { onFilterClear(name); } }; const onOrder = () => { setSorting((state) => (state === 'asc' ? 'desc' : 'asc')); }; const columns = [ { header: '', key: '', cell: (row) => ( <> <TxnStatus status={row.outcomes.status} showLabel={false} /> </> ), tdClassName: 'pl-5 pr-2 py-4 whitespace-nowrap text-sm text-nearblue-600 flex justify-end', }, { header: <>{t ? t('txns:hash') : 'TXN HASH'}</>, key: 'transaction_hash', cell: (row) => ( <span> <Tooltip.Provider> <Tooltip.Root> <Tooltip.Trigger asChild> <span className="truncate max-w-[120px] inline-block align-bottom text-green-500"> <a href={`/txns/${row.transaction_hash}`} className="hover:no-underline" > <a className="text-green-500 font-medium hover:no-underline"> {row.transaction_hash} </a> </a> </span> </Tooltip.Trigger> <Tooltip.Content className="h-auto max-w-xs bg-black bg-opacity-90 z-10 text-xs text-white p-2 break-words" align="start" side="bottom" > {row.transaction_hash} </Tooltip.Content> </Tooltip.Root> </Tooltip.Provider> </span> ), tdClassName: 'px-5 py-4 whitespace-nowrap text-sm text-nearblue-600 ', thClassName: 'px-5 py-4 text-left text-xs font-semibold text-nearblue-600 uppercase tracking-wider whitespace-nowrap', }, { header: ( <Popover.Root> <Popover.Trigger asChild className="flex items-center px-6 py-4 text-left text-xs font-semibold text-nearblue-600 uppercase tracking-wider focus:outline-none" > <button className="IconButton" aria-label="Update dimensions"> {t ? t('txns:type') : 'METHOD'} <Filter className="h-4 w-4 fill-current ml-2" /> </button> </Popover.Trigger> <Popover.Content className="bg-white shadow-lg border rounded-b-lg p-2" sideOffset={5} > <div className="flex flex-col"> <input name="event" value={filters ? filters?.event : ''} onChange={onInputChange} placeholder="Search by method" className="border rounded h-8 mb-2 px-2 text-nearblue-600 text-xs" /> <div className="flex"> <button type="submit" onClick={(e) => onFilter(e, 'event')} className="flex items-center justify-center flex-1 rounded bg-green-500 h-7 text-white text-xs mr-2" > <Filter className="h-3 w-3 fill-current mr-2" />{' '} {t ? t('txns:filter.filter') : 'Filter'} </button> <button name="type" type="button" onClick={() => onClear('method')} className="flex-1 rounded bg-gray-300 text-xs h-7" > {t ? t('txns:filter.clear') : 'Clear'} </button> </div> </div> </Popover.Content> </Popover.Root> ), key: 'event_kind', cell: (row) => ( <span> <Tooltip.Provider> <Tooltip.Root> <Tooltip.Trigger asChild> <span className="bg-blue-900/10 text-xs text-nearblue-600 rounded-xl px-2 py-1 max-w-[120px] inline-flex truncate"> <span className="block truncate">{row.event_kind}</span> </span> </Tooltip.Trigger> <Tooltip.Content className="h-auto max-w-xs bg-black bg-opacity-90 z-10 text-xs text-white px-3 py-2 break-words" align="center" side="bottom" > {row.event_kind} </Tooltip.Content> </Tooltip.Root> </Tooltip.Provider> </span> ), tdClassName: 'px-5 py-4 whitespace-nowrap text-sm text-nearblue-600 ', }, { header: ( <Popover.Root> <Popover.Trigger asChild className="flex items-center px-6 py-4 text-left text-xs font-semibold text-nearblue-600 uppercase tracking-wider focus:outline-none" > <button className="IconButton" aria-label="Update dimensions"> {t ? t('txns:from') : 'FROM'} <Filter className="h-4 w-4 fill-current ml-2" /> </button> </Popover.Trigger> <Popover.Content className="bg-white shadow-lg border rounded-b-lg p-2" sideOffset={5} > <input name="from" value={filters ? filters?.from : ''} onChange={onInputChange} placeholder={ t ? t('txns:filter.placeholder') : 'Search by address e.g. Ⓝ..' } className="border rounded h-8 mb-2 px-2 text-nearblue-600 text-xs" /> <div className="flex"> <button type="submit" onClick={(e) => onFilter(e, 'from')} className="flex items-center justify-center flex-1 rounded bg-green-500 h-7 text-white text-xs mr-2" > <Filter className="h-3 w-3 fill-current mr-2" />{' '} {t ? t('txns:filter.filter') : 'Filter'} </button> <button name="from" type="button" onClick={() => onClear('from')} className="flex-1 rounded bg-gray-300 text-xs h-7" > {t ? t('txns:filter.clear') : 'Clear'} </button> </div> </Popover.Content> </Popover.Root> ), key: 'token_old_owner_account_id', cell: (row) => ( <> {row.token_old_owner_account_id ? ( <Tooltip.Provider> <Tooltip.Root> <Tooltip.Trigger asChild> <span className="truncate max-w-[120px] inline-block align-bottom text-green-500"> <a href={`/address/${row.token_old_owner_account_id}`} className="hover:no-underline" > <a className="text-green-500 hover:no-underline"> {row.token_old_owner_account_id} </a> </a> </span> </Tooltip.Trigger> <Tooltip.Content className="h-auto max-w-xs bg-black bg-opacity-90 z-10 text-xs text-white px-3 py-2 break-words" align="start" side="bottom" > {row.token_old_owner_account_id} </Tooltip.Content> </Tooltip.Root> </Tooltip.Provider> ) : ( 'system' )} </> ), tdClassName: 'px-5 py-4 whitespace-nowrap text-sm text-nearblue-600 font-medium', }, { header: '', key: 'token_old_owner_account_id', cell: (row) => ( <> {row.token_old_owner_account_id === row.token_new_owner_account_id ? ( <span className="uppercase rounded w-10 py-2 h-6 flex items-center justify-center bg-green-200 text-white text-xs font-semibold"> {t ? t('txns:txnSelf') : 'Self'} </span> ) : id === row.token_old_owner_account_id ? ( <span className="uppercase rounded w-10 h-6 flex items-center justify-center bg-yellow-100 text-yellow-700 text-xs font-semibold"> {t ? t('txns:txnOut') : 'OUT'} </span> ) : ( <span className="uppercase rounded w-10 h-6 flex items-center justify-center bg-neargreen text-white text-xs font-semibold"> {t ? t('txns:txnIn') : 'IN'} </span> )} </> ), tdClassName: 'text-center', }, { header: ( <Popover.Root> <Popover.Trigger asChild className="flex items-center px-6 py-4 text-left text-xs font-semibold text-nearblue-600 uppercase tracking-wider focus:outline-none" > <button className="IconButton" aria-label="Update dimensions"> {t ? t('txns:to') : 'To'} <Filter className="h-4 w-4 fill-current ml-2" /> </button> </Popover.Trigger> <Popover.Content className="bg-white shadow-lg border rounded-b-lg p-2" sideOffset={5} > <input name="to" value={filters ? filters?.to : ''} onChange={onInputChange} placeholder={ t ? t('txns:filter.placeholder') : 'Search by address e.g. Ⓝ..' } className="border rounded h-8 mb-2 px-2 text-nearblue-600 text-xs" /> <div className="flex"> <button type="submit" onClick={(e) => onFilter(e, 'to')} className="flex items-center justify-center flex-1 rounded bg-green-500 h-7 text-white text-xs mr-2" > <Filter className="h-3 w-3 fill-current mr-2" />{' '} {t ? t('txns:filter.filter') : 'Filter'} </button> <button name="to" type="button" onClick={() => onClear('to')} className="flex-1 rounded bg-gray-300 text-xs h-7" > {t ? t('txns:filter.clear') : 'Clear'} </button> </div> </Popover.Content> </Popover.Root> ), key: 'token_new_owner_account_id', cell: (row) => ( <> {row.token_new_owner_account_id ? ( <Tooltip.Provider> <Tooltip.Root> <Tooltip.Trigger asChild> <span> <a href={`/address/${row.token_new_owner_account_id}`} className="hover:no-underline" > <a className="text-green-500 hover:no-underline"> {row.token_new_owner_account_id} </a> </a> </span> </Tooltip.Trigger> <Tooltip.Content className="h-auto max-w-xs bg-black bg-opacity-90 z-10 text-xs text-white px-3 py-2 break-words" align="start" side="bottom" > {row.token_new_owner_account_id} </Tooltip.Content> </Tooltip.Root> </Tooltip.Provider> ) : ( 'system' )} </> ), tdClassName: 'px-5 py-4 whitespace-nowrap text-sm text-nearblue-600 font-medium', }, { header: <>Token ID</>, key: 'token_id', cell: (row) => ( <Tooltip.Provider> <Tooltip.Root> <Tooltip.Trigger asChild> <span> <a href={`/nft-token/${row.nft?.contract}/${row.token_id}`} className="hover:no-underline" > <a className="text-green-500 font-medium hover:no-underline whitespace-nowrap"> {row.token_id} </a> </a> </span> </Tooltip.Trigger> <Tooltip.Content className="h-auto max-w-xs bg-black bg-opacity-90 z-10 text-xs text-white px-3 py-2 break-words" align="start" side="bottom" > {row.token_id} </Tooltip.Content> </Tooltip.Root> </Tooltip.Provider> ), tdClassName: 'px-5 py-4 text-sm text-nearblue-600 max-w-[110px] inline-block truncate', thClassName: 'px-5 py-4 text-left text-xs font-semibold text-nearblue-600 uppercase tracking-wider', }, { header: <>Token</>, key: 'block_height', cell: (row) => { return ( row.nft && ( <div className="flex flex-row items-center"> <span className="inline-flex mr-1"> <TokenImage src={row.nft?.icon} alt={row.nft?.name} className="w-4 h-4" appUrl={config.appUrl} /> </span> <Tooltip.Provider> <Tooltip.Root> <Tooltip.Trigger asChild> <div className="text-sm text-nearblue-600 max-w-[110px] inline-block truncate"> <a href={`/nft-token/${row.nft?.contract}`} className="hover:no-underline" > <a className="text-green-500 font-medium hover:no-underline"> {row.nft?.name} </a> </a> </div> </Tooltip.Trigger> <Tooltip.Content className="h-auto max-w-xs bg-black bg-opacity-90 z-10 text-xs text-white px-3 py-2 break-words" align="start" side="bottom" > {row.nft?.name} </Tooltip.Content> </Tooltip.Root> </Tooltip.Provider> {row.nft?.symbol && ( <Tooltip.Provider> <Tooltip.Root> <Tooltip.Trigger asChild> <div className="text-sm text-nearblue-700 max-w-[80px] inline-block truncate"> {row.nft.symbol} </div> </Tooltip.Trigger> <Tooltip.Content className="h-auto max-w-xs bg-black bg-opacity-90 z-10 text-xs text-white px-3 py-2 break-words" align="start" side="bottom" > {row.nft.symbol} </Tooltip.Content> </Tooltip.Root> </Tooltip.Provider> )} </div> ) ); }, tdClassName: 'px-5 py-4 whitespace-nowrap text-sm text-nearblue-600 ', thClassName: 'px-5 py-4 text-left text-xs font-semibold text-nearblue-600 uppercase tracking-wider', }, { header: ( <div className="w-full inline-flex px-5 py-4"> <Tooltip.Provider> <Tooltip.Root> <Tooltip.Trigger asChild> <button type="button" onClick={toggleShowAge} className="text-left text-xs w-full flex items-center font-semibold uppercase tracking-wider text-green-500 focus:outline-none whitespace-nowrap" > {showAge ? t ? t('txns:age') : 'AGE' : t ? t('txns:ageDT') : 'DATE TIME (UTC)'} {showAge && <Clock className="text-green-500 ml-2" />} </button> </Tooltip.Trigger> <Tooltip.Content className="h-auto max-w-xs bg-black bg-opacity-90 z-10 text-xs text-white px-3 py-2 break-words" align="center" side="top" > {showAge ? 'Click to show Datetime Format' : 'Click to show Age Format'} </Tooltip.Content> </Tooltip.Root> </Tooltip.Provider> <button type="button" onClick={onOrder} className="px-2"> <div className="text-nearblue-600 font-semibold"> <SortIcon order={sorting} /> </div> </button> </div> ), key: 'block_timestamp', cell: (row) => ( <span> <Tooltip.Provider> <Tooltip.Root> <Tooltip.Trigger asChild> <span> {!showAge ? row?.block_timestamp ? formatTimestampToString( nanoToMilli(row?.block_timestamp), ) : '' : row?.block_timestamp ? getTimeAgoString(nanoToMilli(row?.block_timestamp)) : ''} </span> </Tooltip.Trigger> <Tooltip.Content className="h-auto max-w-xs bg-black bg-opacity-90 z-10 text-xs text-white px-3 py-2 break-words" align="start" side="bottom" > {showAge ? row?.block_timestamp ? formatTimestampToString(nanoToMilli(row?.block_timestamp)) : '' : row?.block_timestamp ? getTimeAgoString(nanoToMilli(row?.block_timestamp)) : ''} </Tooltip.Content> </Tooltip.Root> </Tooltip.Provider> </span> ), tdClassName: 'px-5 py-4 whitespace-nowrap text-sm text-nearblue-600 ', thClassName: 'whitespace-nowrap', }, ]; return ( <div className="bg-white soft-shadow rounded-xl pb-1"> {isLoading ? ( <div className="pl-6 max-w-lg w-full py-5 "> <Skeleton className="h-4" /> </div> ) : ( <div className={`flex flex-col lg:flex-row pt-4`}> <div className="flex flex-col"> <p className="leading-7 pl-6 text-sm mb-4 text-nearblue-600 "> A total of {localFormat(totalCount.toString())} transactions found </p> </div> <div className=" flex items-center px-2 text-sm mb-4 text-nearblue-600 lg:ml-auto"> {filters && Object.keys(filters).length > 0 && ( <div className="flex items-center px-2 text-sm text-gray-500 lg:ml-auto"> Filtered By: <span className="flex items-center bg-gray-100 rounded-full px-3 py-1 ml-1 space-x-2"> {filters && Object.keys(filters).map((key) => ( <span className="flex" key={key}> {capitalizeFirstLetter(key)}:{' '} <span className="inline-block truncate max-w-[120px]"> <span className="font-semibold">{filters[key]}</span> </span> </span> ))} <CloseCircle className="w-4 h-4 fill-current cursor-pointer" onClick={onClear} /> </span> </div> )} <span className="text-xs text-nearblue-600"> <a href="/nft-token/exportdata/address/id" className="hover:no-underline" > <a target="_blank" className="cursor-pointer mx-1 flex items-center text-nearblue-600 font-medium py-2 border border-neargray-700 px-4 rounded-md bg-white hover:bg-neargray-800 hover:no-underline" > <p>CSV Export </p> <span className="ml-2"> <Download /> </span> </a> </a> </span> </div> </div> )} { <Widget src={`${config.ownerId}/widget/bos-components.components.Shared.Table`} props={{ columns: columns, data: txns[currentPage], isLoading: isLoading, isPagination: true, count: totalCount, page: currentPage, limit: 25, pageLimit: 200, setPage: setPage, Error: errorMessage, }} /> } </div> ); } return MainComponent(props, context);