doge: static/js/offerstable.js

This commit is contained in:
nahuhh 2024-11-28 15:37:38 +00:00 committed by tecnovert
parent 36ec1e8683
commit 95db6655e7

View file

@ -12,7 +12,7 @@ let filterTimeout = null;
// Time Constants // Time Constants
const MIN_REFRESH_INTERVAL = 60; // 60 sec const MIN_REFRESH_INTERVAL = 60; // 60 sec
const CACHE_DURATION = 5 * 60 * 1000; // 5 minutes const CACHE_DURATION = 5 * 60 * 1000; // 5 minutes
const FALLBACK_CACHE_DURATION = 24 * 60 * 60 * 1000; // 24 hours const FALLBACK_CACHE_DURATION = 24 * 60 * 60 * 1000; // 24 hours
// Application Constants // Application Constants
@ -67,7 +67,8 @@ const coinNameToDisplayName = {
const coinIdToName = { const coinIdToName = {
1: 'particl', 2: 'bitcoin', 3: 'litecoin', 4: 'decred', 1: 'particl', 2: 'bitcoin', 3: 'litecoin', 4: 'decred',
6: 'monero', 7: 'particl blind', 8: 'particl anon', 6: 'monero', 7: 'particl blind', 8: 'particl anon',
9: 'wownero', 11: 'pivx', 13: 'firo', 17: 'bitcoincash' 9: 'wownero', 11: 'pivx', 13: 'firo', 17: 'bitcoincash',
18: 'dogecoin'
}; };
// DOM ELEMENT REFERENCES // DOM ELEMENT REFERENCES
@ -92,7 +93,7 @@ const WebSocketManager = {
reconnectDelay: 5000, reconnectDelay: 5000,
maxQueueSize: 1000, maxQueueSize: 1000,
isIntentionallyClosed: false, isIntentionallyClosed: false,
connectionState: { connectionState: {
isConnecting: false, isConnecting: false,
lastConnectAttempt: null, lastConnectAttempt: null,
@ -271,7 +272,7 @@ const WebSocketManager = {
try { try {
const response = await fetch(endpoint); const response = await fetch(endpoint);
if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`); if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
const newData = await response.json(); const newData = await response.json();
const fetchedOffers = Array.isArray(newData) ? newData : Object.values(newData); const fetchedOffers = Array.isArray(newData) ? newData : Object.values(newData);
@ -300,7 +301,7 @@ const WebSocketManager = {
this.reconnectAttempts++; this.reconnectAttempts++;
if (this.reconnectAttempts <= this.maxReconnectAttempts) { if (this.reconnectAttempts <= this.maxReconnectAttempts) {
console.log(`Attempting to reconnect (${this.reconnectAttempts}/${this.maxReconnectAttempts})`); console.log(`Attempting to reconnect (${this.reconnectAttempts}/${this.maxReconnectAttempts})`);
const delay = Math.min( const delay = Math.min(
this.reconnectDelay * Math.pow(1.5, this.reconnectAttempts - 1), this.reconnectDelay * Math.pow(1.5, this.reconnectAttempts - 1),
30000 30000
@ -324,11 +325,11 @@ const WebSocketManager = {
cleanup() { cleanup() {
console.log('Cleaning up WebSocket resources'); console.log('Cleaning up WebSocket resources');
clearTimeout(this.debounceTimeout); clearTimeout(this.debounceTimeout);
clearTimeout(this.reconnectTimeout); clearTimeout(this.reconnectTimeout);
clearTimeout(this.connectionState.connectTimeout); clearTimeout(this.connectionState.connectTimeout);
this.messageQueue = []; this.messageQueue = [];
this.processingQueue = false; this.processingQueue = false;
this.connectionState.isConnecting = false; this.connectionState.isConnecting = false;
@ -365,7 +366,7 @@ const CacheManager = {
set: function(key, value, customTtl = null) { set: function(key, value, customTtl = null) {
try { try {
this.cleanup(); this.cleanup();
const item = { const item = {
value: value, value: value,
timestamp: Date.now(), timestamp: Date.now(),
@ -396,7 +397,7 @@ const CacheManager = {
return false; return false;
} }
}, },
get: function(key) { get: function(key) {
try { try {
const itemStr = localStorage.getItem(key); const itemStr = localStorage.getItem(key);
@ -404,14 +405,14 @@ const CacheManager = {
const item = JSON.parse(itemStr); const item = JSON.parse(itemStr);
const now = Date.now(); const now = Date.now();
if (now < item.expiresAt) { if (now < item.expiresAt) {
return { return {
value: item.value, value: item.value,
remainingTime: item.expiresAt - now remainingTime: item.expiresAt - now
}; };
} }
localStorage.removeItem(key); localStorage.removeItem(key);
} catch (error) { } catch (error) {
localStorage.removeItem(key); localStorage.removeItem(key);
@ -433,7 +434,7 @@ const CacheManager = {
const itemStr = localStorage.getItem(key); const itemStr = localStorage.getItem(key);
const size = new Blob([itemStr]).size; const size = new Blob([itemStr]).size;
const item = JSON.parse(itemStr); const item = JSON.parse(itemStr);
if (now >= item.expiresAt) { if (now >= item.expiresAt) {
localStorage.removeItem(key); localStorage.removeItem(key);
continue; continue;
@ -445,7 +446,7 @@ const CacheManager = {
expiresAt: item.expiresAt, expiresAt: item.expiresAt,
timestamp: item.timestamp timestamp: item.timestamp
}); });
totalSize += size; totalSize += size;
itemCount++; itemCount++;
} catch (error) { } catch (error) {
@ -455,7 +456,7 @@ const CacheManager = {
if (aggressive || totalSize > this.maxSize || itemCount > this.maxItems) { if (aggressive || totalSize > this.maxSize || itemCount > this.maxItems) {
items.sort((a, b) => b.timestamp - a.timestamp); items.sort((a, b) => b.timestamp - a.timestamp);
while ((totalSize > this.maxSize || itemCount > this.maxItems) && items.length > 0) { while ((totalSize > this.maxSize || itemCount > this.maxItems) && items.length > 0) {
const item = items.pop(); const item = items.pop();
localStorage.removeItem(item.key); localStorage.removeItem(item.key);
@ -473,7 +474,7 @@ const CacheManager = {
keys.push(key); keys.push(key);
} }
} }
keys.forEach(key => localStorage.removeItem(key)); keys.forEach(key => localStorage.removeItem(key));
}, },
@ -491,10 +492,10 @@ const CacheManager = {
const itemStr = localStorage.getItem(key); const itemStr = localStorage.getItem(key);
const size = new Blob([itemStr]).size; const size = new Blob([itemStr]).size;
const item = JSON.parse(itemStr); const item = JSON.parse(itemStr);
totalSize += size; totalSize += size;
itemCount++; itemCount++;
if (now >= item.expiresAt) { if (now >= item.expiresAt) {
expiredCount++; expiredCount++;
} }
@ -528,7 +529,7 @@ window.tableRateModule = {
'Bitcoin Cash': 'BCH', 'Bitcoin Cash': 'BCH',
'Dogecoin': 'DOGE' 'Dogecoin': 'DOGE'
}, },
cache: {}, cache: {},
processedOffers: new Set(), processedOffers: new Set(),
@ -564,7 +565,7 @@ window.tableRateModule = {
this.processedOffers.add(offerId); this.processedOffers.add(offerId);
return true; return true;
}, },
formatUSD(value) { formatUSD(value) {
if (Math.abs(value) < 0.000001) { if (Math.abs(value) < 0.000001) {
return value.toExponential(8) + ' USD'; return value.toExponential(8) + ' USD';
@ -654,23 +655,23 @@ async function initializePriceData() {
while (retryCount < PRICE_INIT_RETRIES) { while (retryCount < PRICE_INIT_RETRIES) {
try { try {
prices = await fetchLatestPrices(); prices = await fetchLatestPrices();
if (prices && Object.keys(prices).length > 0) { if (prices && Object.keys(prices).length > 0) {
console.log('Successfully fetched initial price data'); console.log('Successfully fetched initial price data');
latestPrices = prices; latestPrices = prices;
CacheManager.set(PRICES_CACHE_KEY, prices, CACHE_DURATION); CacheManager.set(PRICES_CACHE_KEY, prices, CACHE_DURATION);
return true; return true;
} }
retryCount++; retryCount++;
if (retryCount < PRICE_INIT_RETRIES) { if (retryCount < PRICE_INIT_RETRIES) {
await new Promise(resolve => setTimeout(resolve, PRICE_INIT_RETRY_DELAY)); await new Promise(resolve => setTimeout(resolve, PRICE_INIT_RETRY_DELAY));
} }
} catch (error) { } catch (error) {
console.error(`Error fetching prices (attempt ${retryCount + 1}):`, error); console.error(`Error fetching prices (attempt ${retryCount + 1}):`, error);
retryCount++; retryCount++;
if (retryCount < PRICE_INIT_RETRIES) { if (retryCount < PRICE_INIT_RETRIES) {
await new Promise(resolve => setTimeout(resolve, PRICE_INIT_RETRY_DELAY)); await new Promise(resolve => setTimeout(resolve, PRICE_INIT_RETRY_DELAY));
} }
@ -706,7 +707,7 @@ function checkOfferAgainstFilters(offer, filters) {
const currentTime = Math.floor(Date.now() / 1000); const currentTime = Math.floor(Date.now() / 1000);
const isExpired = offer.expire_at <= currentTime; const isExpired = offer.expire_at <= currentTime;
const isRevoked = Boolean(offer.is_revoked); const isRevoked = Boolean(offer.is_revoked);
switch (filters.status) { switch (filters.status) {
case 'active': case 'active':
return !isExpired && !isRevoked; return !isExpired && !isRevoked;
@ -726,7 +727,7 @@ function initializeFlowbiteTooltips() {
console.warn('Tooltip is not defined. Make sure the required library is loaded.'); console.warn('Tooltip is not defined. Make sure the required library is loaded.');
return; return;
} }
const tooltipElements = document.querySelectorAll('[data-tooltip-target]'); const tooltipElements = document.querySelectorAll('[data-tooltip-target]');
tooltipElements.forEach((el) => { tooltipElements.forEach((el) => {
const tooltipId = el.getAttribute('data-tooltip-target'); const tooltipId = el.getAttribute('data-tooltip-target');
@ -740,10 +741,10 @@ function initializeFlowbiteTooltips() {
// DATA PROCESSING FUNCTIONS // DATA PROCESSING FUNCTIONS
async function checkExpiredAndFetchNew() { async function checkExpiredAndFetchNew() {
if (isSentOffers) return Promise.resolve(); if (isSentOffers) return Promise.resolve();
console.log('Starting checkExpiredAndFetchNew'); console.log('Starting checkExpiredAndFetchNew');
const OFFERS_CACHE_KEY = 'offers_received'; const OFFERS_CACHE_KEY = 'offers_received';
try { try {
const response = await fetch('/json/offers'); const response = await fetch('/json/offers');
const data = await response.json(); const data = await response.json();
@ -772,9 +773,9 @@ async function checkExpiredAndFetchNew() {
CacheManager.set(OFFERS_CACHE_KEY, newListings, CACHE_DURATION); CacheManager.set(OFFERS_CACHE_KEY, newListings, CACHE_DURATION);
const currentFilters = new FormData(filterForm); const currentFilters = new FormData(filterForm);
const hasActiveFilters = currentFilters.get('coin_to') !== 'any' || const hasActiveFilters = currentFilters.get('coin_to') !== 'any' ||
currentFilters.get('coin_from') !== 'any'; currentFilters.get('coin_from') !== 'any';
if (hasActiveFilters) { if (hasActiveFilters) {
jsonData = filterAndSortData(); jsonData = filterAndSortData();
} else { } else {
@ -784,7 +785,7 @@ async function checkExpiredAndFetchNew() {
updateOffersTable(); updateOffersTable();
updateJsonView(); updateJsonView();
updatePaginationInfo(); updatePaginationInfo();
if (jsonData.length === 0) { if (jsonData.length === 0) {
handleNoOffersScenario(); handleNoOffersScenario();
} }
@ -810,7 +811,7 @@ function getValidOffers() {
function filterAndSortData() { function filterAndSortData() {
//console.log('[Debug] Starting filter with data length:', originalJsonData.length); //console.log('[Debug] Starting filter with data length:', originalJsonData.length);
const formData = new FormData(filterForm); const formData = new FormData(filterForm);
const filters = Object.fromEntries(formData); const filters = Object.fromEntries(formData);
//console.log('[Debug] Active filters:', filters); //console.log('[Debug] Active filters:', filters);
@ -825,7 +826,7 @@ function filterAndSortData() {
let filteredData = [...originalJsonData]; let filteredData = [...originalJsonData];
const sentFromFilter = filters.sent_from || 'any'; const sentFromFilter = filters.sent_from || 'any';
filteredData = filteredData.filter(offer => { filteredData = filteredData.filter(offer => {
if (sentFromFilter === 'public') { if (sentFromFilter === 'public') {
return offer.is_public; return offer.is_public;
@ -842,13 +843,13 @@ function filterAndSortData() {
const coinFrom = (offer.coin_from || '').toLowerCase(); const coinFrom = (offer.coin_from || '').toLowerCase();
const coinTo = (offer.coin_to || '').toLowerCase(); const coinTo = (offer.coin_to || '').toLowerCase();
if (filters.coin_to !== 'any') { if (filters.coin_to !== 'any') {
if (!coinMatches(coinTo, filters.coin_to)) { if (!coinMatches(coinTo, filters.coin_to)) {
return false; return false;
} }
} }
if (filters.coin_from !== 'any') { if (filters.coin_from !== 'any') {
if (!coinMatches(coinFrom, filters.coin_from)) { if (!coinMatches(coinFrom, filters.coin_from)) {
return false; return false;
@ -859,7 +860,7 @@ function filterAndSortData() {
const currentTime = Math.floor(Date.now() / 1000); const currentTime = Math.floor(Date.now() / 1000);
const isExpired = offer.expire_at <= currentTime; const isExpired = offer.expire_at <= currentTime;
const isRevoked = Boolean(offer.is_revoked); const isRevoked = Boolean(offer.is_revoked);
switch (filters.status) { switch (filters.status) {
case 'active': case 'active':
return !isExpired && !isRevoked; return !isExpired && !isRevoked;
@ -878,7 +879,7 @@ function filterAndSortData() {
if (currentSortColumn !== null) { if (currentSortColumn !== null) {
filteredData.sort((a, b) => { filteredData.sort((a, b) => {
let comparison = 0; let comparison = 0;
switch(currentSortColumn) { switch(currentSortColumn) {
case 0: // Time case 0: // Time
comparison = a.created_at - b.created_at; comparison = a.created_at - b.created_at;
@ -891,32 +892,32 @@ function filterAndSortData() {
const aToSymbol = getCoinSymbolLowercase(a.coin_to); const aToSymbol = getCoinSymbolLowercase(a.coin_to);
const bFromSymbol = getCoinSymbolLowercase(b.coin_from); const bFromSymbol = getCoinSymbolLowercase(b.coin_from);
const bToSymbol = getCoinSymbolLowercase(b.coin_to); const bToSymbol = getCoinSymbolLowercase(b.coin_to);
const aFromPrice = latestPrices[aFromSymbol]?.usd || 0; const aFromPrice = latestPrices[aFromSymbol]?.usd || 0;
const aToPrice = latestPrices[aToSymbol]?.usd || 0; const aToPrice = latestPrices[aToSymbol]?.usd || 0;
const bFromPrice = latestPrices[bFromSymbol]?.usd || 0; const bFromPrice = latestPrices[bFromSymbol]?.usd || 0;
const bToPrice = latestPrices[bToSymbol]?.usd || 0; const bToPrice = latestPrices[bToSymbol]?.usd || 0;
const aMarketRate = aToPrice / aFromPrice; const aMarketRate = aToPrice / aFromPrice;
const bMarketRate = bToPrice / bFromPrice; const bMarketRate = bToPrice / bFromPrice;
const aOfferedRate = parseFloat(a.rate); const aOfferedRate = parseFloat(a.rate);
const bOfferedRate = parseFloat(b.rate); const bOfferedRate = parseFloat(b.rate);
const aPercentDiff = ((aOfferedRate - aMarketRate) / aMarketRate) * 100; const aPercentDiff = ((aOfferedRate - aMarketRate) / aMarketRate) * 100;
const bPercentDiff = ((bOfferedRate - bMarketRate) / bMarketRate) * 100; const bPercentDiff = ((bOfferedRate - bMarketRate) / bMarketRate) * 100;
comparison = aPercentDiff - bPercentDiff; comparison = aPercentDiff - bPercentDiff;
break; break;
case 7: // Trade case 7: // Trade
comparison = a.offer_id.localeCompare(b.offer_id); comparison = a.offer_id.localeCompare(b.offer_id);
break; break;
} }
return currentSortDirection === 'desc' ? -comparison : comparison; return currentSortDirection === 'desc' ? -comparison : comparison;
}); });
} }
//console.log(`[Debug] Filtered data length: ${filteredData.length}`); //console.log(`[Debug] Filtered data length: ${filteredData.length}`);
return filteredData; return filteredData;
} }
@ -1019,7 +1020,7 @@ async function fetchLatestPrices() {
} }
const url = `${config.apiEndpoints.coinGecko}/simple/price?ids=bitcoin,bitcoin-cash,dash,dogecoin,decred,litecoin,particl,pivx,monero,zano,wownero,zcoin&vs_currencies=USD,BTC&api_key=${config.apiKeys.coinGecko}`; const url = `${config.apiEndpoints.coinGecko}/simple/price?ids=bitcoin,bitcoin-cash,dash,dogecoin,decred,litecoin,particl,pivx,monero,zano,wownero,zcoin&vs_currencies=USD,BTC&api_key=${config.apiKeys.coinGecko}`;
try { try {
console.log('Fetching fresh price data...'); console.log('Fetching fresh price data...');
const response = await fetch('/json/readurl', { const response = await fetch('/json/readurl', {
@ -1041,7 +1042,7 @@ async function fetchLatestPrices() {
if (data.Error) { if (data.Error) {
throw new Error(data.Error); throw new Error(data.Error);
} }
if (data && Object.keys(data).length > 0) { if (data && Object.keys(data).length > 0) {
console.log('Fresh price data received'); console.log('Fresh price data received');
@ -1052,7 +1053,7 @@ async function fetchLatestPrices() {
Object.entries(data).forEach(([coin, prices]) => { Object.entries(data).forEach(([coin, prices]) => {
tableRateModule.setFallbackValue(coin, prices.usd); tableRateModule.setFallbackValue(coin, prices.usd);
}); });
return data; return data;
} else { } else {
//console.warn('Received empty price data'); //console.warn('Received empty price data');
@ -1075,18 +1076,18 @@ async function fetchOffers(manualRefresh = false) {
refreshIcon.classList.add('animate-spin'); refreshIcon.classList.add('animate-spin');
refreshText.textContent = 'Refreshing...'; refreshText.textContent = 'Refreshing...';
refreshButton.classList.add('opacity-75', 'cursor-wait'); refreshButton.classList.add('opacity-75', 'cursor-wait');
const endpoint = isSentOffers ? '/json/sentoffers' : '/json/offers'; const endpoint = isSentOffers ? '/json/sentoffers' : '/json/offers';
const response = await fetch(endpoint); const response = await fetch(endpoint);
const data = await response.json(); const data = await response.json();
jsonData = formatInitialData(data); jsonData = formatInitialData(data);
originalJsonData = [...jsonData]; originalJsonData = [...jsonData];
await updateOffersTable(); await updateOffersTable();
updateJsonView(); updateJsonView();
updatePaginationInfo(); updatePaginationInfo();
} catch (error) { } catch (error) {
console.error('[Debug] Error fetching offers:', error); console.error('[Debug] Error fetching offers:', error);
ui.displayErrorMessage('Failed to fetch offers. Please try again later.'); ui.displayErrorMessage('Failed to fetch offers. Please try again later.');
@ -1120,12 +1121,12 @@ function formatInitialData(data) {
function updateConnectionStatus(status) { function updateConnectionStatus(status) {
const dot = document.getElementById('status-dot'); const dot = document.getElementById('status-dot');
const text = document.getElementById('status-text'); const text = document.getElementById('status-text');
if (!dot || !text) { if (!dot || !text) {
//console.warn('Status indicators not found in DOM'); //console.warn('Status indicators not found in DOM');
return; return;
} }
switch(status) { switch(status) {
case 'connected': case 'connected':
dot.className = 'w-2.5 h-2.5 rounded-full bg-green-500 mr-2'; dot.className = 'w-2.5 h-2.5 rounded-full bg-green-500 mr-2';
@ -1159,10 +1160,10 @@ function updateRowTimes() {
const newPostedTime = formatTime(offer.created_at, true); const newPostedTime = formatTime(offer.created_at, true);
const newExpiresIn = formatTimeLeft(offer.expire_at); const newExpiresIn = formatTimeLeft(offer.expire_at);
const postedElement = row.querySelector('.text-xs:first-child'); const postedElement = row.querySelector('.text-xs:first-child');
const expiresElement = row.querySelector('.text-xs:last-child'); const expiresElement = row.querySelector('.text-xs:last-child');
if (postedElement && postedElement.textContent !== `Posted: ${newPostedTime}`) { if (postedElement && postedElement.textContent !== `Posted: ${newPostedTime}`) {
postedElement.textContent = `Posted: ${newPostedTime}`; postedElement.textContent = `Posted: ${newPostedTime}`;
} }
@ -1212,14 +1213,14 @@ function updatePaginationInfo() {
const showPrev = currentPage > 1; const showPrev = currentPage > 1;
const showNext = currentPage < totalPages && totalItems > 0; const showNext = currentPage < totalPages && totalItems > 0;
prevPageButton.style.display = showPrev ? 'inline-flex' : 'none'; prevPageButton.style.display = showPrev ? 'inline-flex' : 'none';
nextPageButton.style.display = showNext ? 'inline-flex' : 'none'; nextPageButton.style.display = showNext ? 'inline-flex' : 'none';
if (lastRefreshTime) { if (lastRefreshTime) {
lastRefreshTimeSpan.textContent = new Date(lastRefreshTime).toLocaleTimeString(); lastRefreshTimeSpan.textContent = new Date(lastRefreshTime).toLocaleTimeString();
} }
if (newEntriesCountSpan) { if (newEntriesCountSpan) {
newEntriesCountSpan.textContent = totalItems; newEntriesCountSpan.textContent = totalItems;
} }
@ -1255,13 +1256,13 @@ function updateProfitLoss(row, fromCoin, toCoin, fromAmount, toAmount, isOwnOffe
} }
const formattedPercentDiff = percentDiff.toFixed(2); const formattedPercentDiff = percentDiff.toFixed(2);
const percentDiffDisplay = formattedPercentDiff === "0.00" ? "0.00" : const percentDiffDisplay = formattedPercentDiff === "0.00" ? "0.00" :
(percentDiff > 0 ? `+${formattedPercentDiff}` : formattedPercentDiff); (percentDiff > 0 ? `+${formattedPercentDiff}` : formattedPercentDiff);
const colorClass = getProfitColorClass(percentDiff); const colorClass = getProfitColorClass(percentDiff);
profitLossElement.textContent = `${percentDiffDisplay}%`; profitLossElement.textContent = `${percentDiffDisplay}%`;
profitLossElement.className = `profit-loss text-lg font-bold ${colorClass}`; profitLossElement.className = `profit-loss text-lg font-bold ${colorClass}`;
const tooltipId = `percentage-tooltip-${row.getAttribute('data-offer-id')}`; const tooltipId = `percentage-tooltip-${row.getAttribute('data-offer-id')}`;
const tooltipElement = document.getElementById(tooltipId); const tooltipElement = document.getElementById(tooltipId);
if (tooltipElement) { if (tooltipElement) {
@ -1310,7 +1311,7 @@ function updateClearFiltersButton() {
const hasFilters = hasActiveFilters(); const hasFilters = hasActiveFilters();
clearButton.classList.toggle('opacity-50', !hasFilters); clearButton.classList.toggle('opacity-50', !hasFilters);
clearButton.disabled = !hasFilters; clearButton.disabled = !hasFilters;
// Update button styles based on state // Update button styles based on state
if (hasFilters) { if (hasFilters) {
clearButton.classList.add('hover:bg-green-600', 'hover:text-white'); clearButton.classList.add('hover:bg-green-600', 'hover:text-white');
@ -1325,10 +1326,10 @@ function updateClearFiltersButton() {
function handleNoOffersScenario() { function handleNoOffersScenario() {
const formData = new FormData(filterForm); const formData = new FormData(filterForm);
const filters = Object.fromEntries(formData); const filters = Object.fromEntries(formData);
const hasActiveFilters = filters.coin_to !== 'any' || const hasActiveFilters = filters.coin_to !== 'any' ||
filters.coin_from !== 'any' || filters.coin_from !== 'any' ||
(filters.status && filters.status !== 'any'); (filters.status && filters.status !== 'any');
stopRefreshAnimation(); stopRefreshAnimation();
if (hasActiveFilters) { if (hasActiveFilters) {
@ -1336,7 +1337,7 @@ function handleNoOffersScenario() {
<tr> <tr>
<td colspan="9" class="text-center py-8"> <td colspan="9" class="text-center py-8">
<div class="flex items-center justify-center text-gray-500 dark:text-white"> <div class="flex items-center justify-center text-gray-500 dark:text-white">
No offers match the selected filters. Try different filter options or No offers match the selected filters. Try different filter options or
<button onclick="clearFilters()" class="ml-1 text-blue-500 hover:text-blue-700 font-semibold"> <button onclick="clearFilters()" class="ml-1 text-blue-500 hover:text-blue-700 font-semibold">
clear filters clear filters
</button> </button>
@ -1357,7 +1358,7 @@ async function updateOffersTable() {
try { try {
const PRICES_CACHE_KEY = 'prices_coingecko'; const PRICES_CACHE_KEY = 'prices_coingecko';
const cachedPrices = CacheManager.get(PRICES_CACHE_KEY); const cachedPrices = CacheManager.get(PRICES_CACHE_KEY);
if (!cachedPrices || !cachedPrices.remainingTime || cachedPrices.remainingTime < 60000) { if (!cachedPrices || !cachedPrices.remainingTime || cachedPrices.remainingTime < 60000) {
console.log('Fetching fresh price data...'); console.log('Fetching fresh price data...');
const priceData = await fetchLatestPrices(); const priceData = await fetchLatestPrices();
@ -1374,12 +1375,12 @@ async function updateOffersTable() {
const endIndex = Math.min(startIndex + itemsPerPage, validOffers.length); const endIndex = Math.min(startIndex + itemsPerPage, validOffers.length);
const itemsToDisplay = validOffers.slice(startIndex, endIndex); const itemsToDisplay = validOffers.slice(startIndex, endIndex);
const identityPromises = itemsToDisplay.map(offer => const identityPromises = itemsToDisplay.map(offer =>
offer.addr_from ? getIdentityData(offer.addr_from) : Promise.resolve(null) offer.addr_from ? getIdentityData(offer.addr_from) : Promise.resolve(null)
); );
const identities = await Promise.all(identityPromises); const identities = await Promise.all(identityPromises);
if (validOffers.length === 0) { if (validOffers.length === 0) {
handleNoOffersScenario(); handleNoOffersScenario();
return; return;
@ -1405,7 +1406,7 @@ async function updateOffersTable() {
initializeFlowbiteTooltips(); initializeFlowbiteTooltips();
updateRowTimes(); updateRowTimes();
updatePaginationControls(totalPages); updatePaginationControls(totalPages);
if (tableRateModule?.initializeTable) { if (tableRateModule?.initializeTable) {
tableRateModule.initializeTable(); tableRateModule.initializeTable();
} }
@ -1482,7 +1483,7 @@ function getIdentityInfo(address, identity) {
function createTableRow(offer, identity = null) { function createTableRow(offer, identity = null) {
const row = document.createElement('tr'); const row = document.createElement('tr');
const uniqueId = `${offer.offer_id}_${offer.created_at}`; const uniqueId = `${offer.offer_id}_${offer.created_at}`;
row.className = 'relative opacity-100 text-gray-500 dark:text-gray-100 hover:bg-coolGray-200 dark:hover:bg-gray-600'; row.className = 'relative opacity-100 text-gray-500 dark:text-gray-100 hover:bg-coolGray-200 dark:hover:bg-gray-600';
row.setAttribute('data-offer-id', uniqueId); row.setAttribute('data-offer-id', uniqueId);
@ -1504,7 +1505,7 @@ function createTableRow(offer, identity = null) {
const coinToDisplay = getDisplayName(coinTo); const coinToDisplay = getDisplayName(coinTo);
const postedTime = formatTime(createdAt, true); const postedTime = formatTime(createdAt, true);
const expiresIn = formatTime(expireAt); const expiresIn = formatTime(expireAt);
const currentTime = Math.floor(Date.now() / 1000); const currentTime = Math.floor(Date.now() / 1000);
const isActuallyExpired = currentTime > expireAt; const isActuallyExpired = currentTime > expireAt;
const fromAmount = parseFloat(amountFrom) || 0; const fromAmount = parseFloat(amountFrom) || 0;
@ -1585,19 +1586,19 @@ function shouldShowPublicTag(offers) {
function truncateText(text, maxLength = 15) { function truncateText(text, maxLength = 15) {
if (typeof text !== 'string') return ''; if (typeof text !== 'string') return '';
return text.length > maxLength return text.length > maxLength
? text.slice(0, maxLength) + '...' ? text.slice(0, maxLength) + '...'
: text; : text;
} }
function createDetailsColumn(offer, identity = null) { function createDetailsColumn(offer, identity = null) {
const addrFrom = offer.addr_from || ''; const addrFrom = offer.addr_from || '';
const identityInfo = getIdentityInfo(addrFrom, identity); const identityInfo = getIdentityInfo(addrFrom, identity);
const showPublicPrivateTags = originalJsonData.some(o => o.is_public !== offer.is_public); const showPublicPrivateTags = originalJsonData.some(o => o.is_public !== offer.is_public);
const tagClass = offer.is_public const tagClass = offer.is_public
? 'bg-green-600 dark:bg-green-600' ? 'bg-green-600 dark:bg-green-600'
: 'bg-red-500 dark:bg-red-500'; : 'bg-red-500 dark:bg-red-500';
const tagText = offer.is_public ? 'Public' : 'Private'; const tagText = offer.is_public ? 'Public' : 'Private';
@ -1605,16 +1606,16 @@ function createDetailsColumn(offer, identity = null) {
identityInfo.label || addrFrom || 'Unspecified' identityInfo.label || addrFrom || 'Unspecified'
); );
const identifierTextClass = identityInfo.label const identifierTextClass = identityInfo.label
? 'text-white dark:text-white' ? 'text-white dark:text-white'
: 'monospace'; : 'monospace';
return ` return `
<td class="py-8 px-4 text-xs text-left hidden xl:block"> <td class="py-8 px-4 text-xs text-left hidden xl:block">
<div class="flex flex-col gap-2 relative"> <div class="flex flex-col gap-2 relative">
${showPublicPrivateTags ? `<span class="inline-flex pl-6 pr-6 py-1 justify-center text-[10px] w-1/4 font-medium text-gray-100 rounded-md ${tagClass}">${tagText}</span> ${showPublicPrivateTags ? `<span class="inline-flex pl-6 pr-6 py-1 justify-center text-[10px] w-1/4 font-medium text-gray-100 rounded-md ${tagClass}">${tagText}</span>
` : ''} ` : ''}
<a data-tooltip-target="tooltip-recipient-${escapeHtml(offer.offer_id)}" href="/identity/${escapeHtml(addrFrom)}" class="flex items-center"> <a data-tooltip-target="tooltip-recipient-${escapeHtml(offer.offer_id)}" href="/identity/${escapeHtml(addrFrom)}" class="flex items-center">
<svg class="w-4 h-4 mr-2 text-gray-400 dark:text-white" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg"> <svg class="w-4 h-4 mr-2 text-gray-400 dark:text-white" fill="currentColor" viewBox="0 0 20 20" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" d="M10 9a3 3 0 100-6 3 3 0 000 6zm-7 9a7 7 0 1114 0H3z" clip-rule="evenodd"></path> <path fill-rule="evenodd" d="M10 9a3 3 0 100-6 3 3 0 000 6zm-7 9a7 7 0 1114 0H3z" clip-rule="evenodd"></path>
@ -1635,8 +1636,8 @@ function createTakerAmountColumn(offer, coinTo, coinFrom) {
<td class="py-0"> <td class="py-0">
<div class="py-3 px-4 text-left"> <div class="py-3 px-4 text-left">
<a data-tooltip-target="tooltip-wallet${escapeHtml(offer.offer_id)}" href="/wallet/${escapeHtml(toSymbol)}" class="items-center monospace"> <a data-tooltip-target="tooltip-wallet${escapeHtml(offer.offer_id)}" href="/wallet/${escapeHtml(toSymbol)}" class="items-center monospace">
<div class="pr-2"> <div class="pr-2">
<div class="text-sm font-semibold">${fromAmount.toFixed(4)}</div> <div class="text-sm font-semibold">${fromAmount.toFixed(4)}</div>
<div class="text-sm text-gray-500 dark:text-gray-400">${coinTo}</div> <div class="text-sm text-gray-500 dark:text-gray-400">${coinTo}</div>
</div> </div>
</a> </a>
@ -1679,8 +1680,8 @@ function createOrderbookColumn(offer, coinFrom, coinTo) {
<td class="p-0"> <td class="p-0">
<div class="py-3 px-4 text-right"> <div class="py-3 px-4 text-right">
<a data-tooltip-target="tooltip-wallet-maker${escapeHtml(offer.offer_id)}" href="/wallet/${escapeHtml(fromSymbol)}" class="items-center monospace"> <a data-tooltip-target="tooltip-wallet-maker${escapeHtml(offer.offer_id)}" href="/wallet/${escapeHtml(fromSymbol)}" class="items-center monospace">
<div class="pr-2"> <div class="pr-2">
<div class="text-sm font-semibold">${toAmount.toFixed(4)}</div> <div class="text-sm font-semibold">${toAmount.toFixed(4)}</div>
<div class="text-sm text-gray-500 dark:text-gray-400">${coinFrom}</div> <div class="text-sm text-gray-500 dark:text-gray-400">${coinFrom}</div>
</div> </div>
</a> </a>
@ -1694,7 +1695,7 @@ function createRateColumn(offer, coinFrom, coinTo) {
const inverseRate = 1 / rate; const inverseRate = 1 / rate;
const fromSymbol = getCoinSymbol(coinFrom); const fromSymbol = getCoinSymbol(coinFrom);
const toSymbol = getCoinSymbol(coinTo); const toSymbol = getCoinSymbol(coinTo);
const getPriceKey = (coin) => { const getPriceKey = (coin) => {
const lowerCoin = coin.toLowerCase(); const lowerCoin = coin.toLowerCase();
if (lowerCoin === 'firo' || lowerCoin === 'zcoin') { if (lowerCoin === 'firo' || lowerCoin === 'zcoin') {
@ -1746,9 +1747,9 @@ function createPercentageColumn(offer) {
function createActionColumn(offer, isActuallyExpired = false) { function createActionColumn(offer, isActuallyExpired = false) {
const isRevoked = Boolean(offer.is_revoked); const isRevoked = Boolean(offer.is_revoked);
const isTreatedAsSentOffer = offer.is_own_offer; const isTreatedAsSentOffer = offer.is_own_offer;
let buttonClass, buttonText; let buttonClass, buttonText;
if (isRevoked) { if (isRevoked) {
buttonClass = 'bg-red-500 text-white hover:bg-red-600 transition duration-200'; buttonClass = 'bg-red-500 text-white hover:bg-red-600 transition duration-200';
buttonText = 'Revoked'; buttonText = 'Revoked';
@ -1786,14 +1787,14 @@ function createTooltips(offer, treatAsSentOffer, coinFrom, coinTo, fromAmount, t
const identityInfo = getIdentityInfo(addrFrom, identity); const identityInfo = getIdentityInfo(addrFrom, identity);
const totalBids = identity ? ( const totalBids = identity ? (
identityInfo.stats.sentBidsSuccessful + identityInfo.stats.sentBidsSuccessful +
identityInfo.stats.recvBidsSuccessful + identityInfo.stats.recvBidsSuccessful +
identityInfo.stats.sentBidsFailed + identityInfo.stats.sentBidsFailed +
identityInfo.stats.recvBidsFailed + identityInfo.stats.recvBidsFailed +
identityInfo.stats.sentBidsRejected + identityInfo.stats.sentBidsRejected +
identityInfo.stats.recvBidsRejected identityInfo.stats.recvBidsRejected
) : 0; ) : 0;
const successRate = totalBids ? ( const successRate = totalBids ? (
((identityInfo.stats.sentBidsSuccessful + identityInfo.stats.recvBidsSuccessful) / totalBids) * 100 ((identityInfo.stats.sentBidsSuccessful + identityInfo.stats.recvBidsSuccessful) / totalBids) * 100
).toFixed(1) : 0; ).toFixed(1) : 0;
@ -1846,14 +1847,14 @@ function createTooltips(offer, treatAsSentOffer, coinFrom, coinTo, fromAmount, t
</div> </div>
<div class="tooltip-arrow" data-popper-arrow></div> <div class="tooltip-arrow" data-popper-arrow></div>
</div> </div>
<div id="tooltip-wallet-${uniqueId}" role="tooltip" class="inline-block absolute invisible z-50 py-2 px-3 text-sm font-medium text-white bg-gray-400 rounded-lg shadow-sm opacity-0 transition-opacity duration-300 tooltip"> <div id="tooltip-wallet-${uniqueId}" role="tooltip" class="inline-block absolute invisible z-50 py-2 px-3 text-sm font-medium text-white bg-gray-400 rounded-lg shadow-sm opacity-0 transition-opacity duration-300 tooltip">
<div class="active-revoked-expired"> <div class="active-revoked-expired">
<span class="bold">${treatAsSentOffer ? 'My' : ''} ${coinTo} Wallet</span> <span class="bold">${treatAsSentOffer ? 'My' : ''} ${coinTo} Wallet</span>
</div> </div>
<div class="tooltip-arrow pl-1" data-popper-arrow></div> <div class="tooltip-arrow pl-1" data-popper-arrow></div>
</div> </div>
<div id="tooltip-offer-${uniqueId}" role="tooltip" class="inline-block absolute z-50 py-2 px-3 text-sm font-medium text-white ${isRevoked ? 'bg-red-500' : (offer.is_own_offer ? 'bg-gray-300' : 'bg-green-700')} rounded-lg shadow-sm opacity-0 transition-opacity duration-300 tooltip"> <div id="tooltip-offer-${uniqueId}" role="tooltip" class="inline-block absolute z-50 py-2 px-3 text-sm font-medium text-white ${isRevoked ? 'bg-red-500' : (offer.is_own_offer ? 'bg-gray-300' : 'bg-green-700')} rounded-lg shadow-sm opacity-0 transition-opacity duration-300 tooltip">
<div class="active-revoked-expired"> <div class="active-revoked-expired">
<span class="bold"> <span class="bold">
@ -1862,14 +1863,14 @@ function createTooltips(offer, treatAsSentOffer, coinFrom, coinTo, fromAmount, t
</div> </div>
<div class="tooltip-arrow pr-6" data-popper-arrow></div> <div class="tooltip-arrow pr-6" data-popper-arrow></div>
</div> </div>
<div id="tooltip-wallet-maker-${uniqueId}" role="tooltip" class="inline-block absolute invisible z-50 py-2 px-3 text-sm font-medium text-white bg-gray-400 rounded-lg shadow-sm opacity-0 transition-opacity duration-300 tooltip"> <div id="tooltip-wallet-maker-${uniqueId}" role="tooltip" class="inline-block absolute invisible z-50 py-2 px-3 text-sm font-medium text-white bg-gray-400 rounded-lg shadow-sm opacity-0 transition-opacity duration-300 tooltip">
<div class="active-revoked-expired"> <div class="active-revoked-expired">
<span class="bold">${treatAsSentOffer ? 'My' : ''} ${coinFrom} Wallet</span> <span class="bold">${treatAsSentOffer ? 'My' : ''} ${coinFrom} Wallet</span>
</div> </div>
<div class="tooltip-arrow pl-1" data-popper-arrow></div> <div class="tooltip-arrow pl-1" data-popper-arrow></div>
</div> </div>
<div id="tooltip-rate-${uniqueId}" role="tooltip" class="inline-block absolute invisible z-50 py-2 px-3 text-sm font-medium text-white bg-gray-400 rounded-lg shadow-sm opacity-0 transition-opacity duration-300 tooltip"> <div id="tooltip-rate-${uniqueId}" role="tooltip" class="inline-block absolute invisible z-50 py-2 px-3 text-sm font-medium text-white bg-gray-400 rounded-lg shadow-sm opacity-0 transition-opacity duration-300 tooltip">
<div class="tooltip-content"> <div class="tooltip-content">
${combinedRateTooltip} ${combinedRateTooltip}
@ -1903,7 +1904,7 @@ function createRecipientTooltip(uniqueId, identityInfo, identity, successRate, t
}; };
return ` return `
<div id="tooltip-recipient-${uniqueId}" role="tooltip" <div id="tooltip-recipient-${uniqueId}" role="tooltip"
class="fixed z-50 py-3 px-4 text-sm font-medium text-white bg-gray-400 rounded-lg shadow-sm opacity-0 transition-opacity duration-300 tooltip max-w-sm pointer-events-none"> class="fixed z-50 py-3 px-4 text-sm font-medium text-white bg-gray-400 rounded-lg shadow-sm opacity-0 transition-opacity duration-300 tooltip max-w-sm pointer-events-none">
<div class="identity-info space-y-2"> <div class="identity-info space-y-2">
${identityInfo.label ? ` ${identityInfo.label ? `
@ -1912,7 +1913,7 @@ function createRecipientTooltip(uniqueId, identityInfo, identity, successRate, t
<div class="text-white">${escapeHtml(identityInfo.label)}</div> <div class="text-white">${escapeHtml(identityInfo.label)}</div>
</div> </div>
` : ''} ` : ''}
<div class="space-y-1"> <div class="space-y-1">
<div class="text-white text-xs tracking-wide font-semibold">Recipient Address:</div> <div class="text-white text-xs tracking-wide font-semibold">Recipient Address:</div>
<div class="monospace text-xs break-all bg-gray-500 p-2 rounded-md text-white"> <div class="monospace text-xs break-all bg-gray-500 p-2 rounded-md text-white">
@ -2001,7 +2002,7 @@ function createTooltipContent(isSentOffers, coinFrom, coinTo, fromAmount, toAmou
const marketRate = fromPriceUSD / toPriceUSD; const marketRate = fromPriceUSD / toPriceUSD;
const offerRate = toAmount / fromAmount; const offerRate = toAmount / fromAmount;
let percentDiff; let percentDiff;
if (isSentOffers || isOwnOffer) { if (isSentOffers || isOwnOffer) {
percentDiff = ((toValueUSD / fromValueUSD) - 1) * 100; percentDiff = ((toValueUSD / fromValueUSD) - 1) * 100;
} else { } else {
@ -2009,7 +2010,7 @@ function createTooltipContent(isSentOffers, coinFrom, coinTo, fromAmount, toAmou
} }
const formattedPercentDiff = percentDiff.toFixed(2); const formattedPercentDiff = percentDiff.toFixed(2);
const percentDiffDisplay = formattedPercentDiff === "0.00" ? "0.00" : const percentDiffDisplay = formattedPercentDiff === "0.00" ? "0.00" :
(percentDiff > 0 ? `+${formattedPercentDiff}` : formattedPercentDiff); (percentDiff > 0 ? `+${formattedPercentDiff}` : formattedPercentDiff);
const profitLabel = (isSentOffers || isOwnOffer) ? "Max Profit" : "Max Loss"; const profitLabel = (isSentOffers || isOwnOffer) ? "Max Profit" : "Max Loss";
@ -2022,8 +2023,8 @@ function createTooltipContent(isSentOffers, coinFrom, coinTo, fromAmount, toAmou
<p class="mt-1">Percentage difference: ${percentDiffDisplay}%</p> <p class="mt-1">Percentage difference: ${percentDiffDisplay}%</p>
<p>${profitLabel}: ${profitUSD > 0 ? '' : '-'}$${Math.abs(profitUSD).toFixed(2)} USD</p> <p>${profitLabel}: ${profitUSD > 0 ? '' : '-'}$${Math.abs(profitUSD).toFixed(2)} USD</p>
<p class="font-bold mt-2">Calculation:</p> <p class="font-bold mt-2">Calculation:</p>
<p>Percentage = ${(isSentOffers || isOwnOffer) ? <p>Percentage = ${(isSentOffers || isOwnOffer) ?
"((To Amount in USD / From Amount in USD) - 1) * 100" : "((To Amount in USD / From Amount in USD) - 1) * 100" :
"((From Amount in USD / To Amount in USD) - 1) * 100"}</p> "((From Amount in USD / To Amount in USD) - 1) * 100"}</p>
<p>USD ${profitLabel} = To Amount in USD - From Amount in USD</p> <p>USD ${profitLabel} = To Amount in USD - From Amount in USD</p>
<p class="font-bold mt-1">Interpretation:</p> <p class="font-bold mt-1">Interpretation:</p>
@ -2034,8 +2035,8 @@ function createTooltipContent(isSentOffers, coinFrom, coinTo, fromAmount, toAmou
<p><span class="text-green-500">Positive percentage:</span> You're buying below market rate (savings)</p> <p><span class="text-green-500">Positive percentage:</span> You're buying below market rate (savings)</p>
<p><span class="text-red-500">Negative percentage:</span> You're buying above market rate (premium)</p> <p><span class="text-red-500">Negative percentage:</span> You're buying above market rate (premium)</p>
`} `}
<p class="mt-1"><strong>Note:</strong> ${(isSentOffers || isOwnOffer) ? <p class="mt-1"><strong>Note:</strong> ${(isSentOffers || isOwnOffer) ?
"As a seller, a positive percentage means <br/> you're selling for more than the current market value." : "As a seller, a positive percentage means <br/> you're selling for more than the current market value." :
"As a buyer, a positive percentage indicates </br> potential savings compared to current market rates."}</p> "As a buyer, a positive percentage indicates </br> potential savings compared to current market rates."}</p>
<p class="mt-1"><strong>Market Rate:</strong> 1 ${coinFrom} = ${marketRate.toFixed(8)} ${coinTo}</p> <p class="mt-1"><strong>Market Rate:</strong> 1 ${coinFrom} = ${marketRate.toFixed(8)} ${coinTo}</p>
<p><strong>Offer Rate:</strong> 1 ${coinFrom} = ${offerRate.toFixed(8)} ${coinTo}</p> <p><strong>Offer Rate:</strong> 1 ${coinFrom} = ${offerRate.toFixed(8)} ${coinTo}</p>
@ -2045,7 +2046,7 @@ function createTooltipContent(isSentOffers, coinFrom, coinTo, fromAmount, toAmou
function createCombinedRateTooltip(offer, coinFrom, coinTo, isSentOffers, treatAsSentOffer) { function createCombinedRateTooltip(offer, coinFrom, coinTo, isSentOffers, treatAsSentOffer) {
const rate = parseFloat(offer.rate); const rate = parseFloat(offer.rate);
const inverseRate = 1 / rate; const inverseRate = 1 / rate;
const getPriceKey = (coin) => { const getPriceKey = (coin) => {
const lowerCoin = coin.toLowerCase(); const lowerCoin = coin.toLowerCase();
if (lowerCoin === 'firo' || lowerCoin === 'zcoin') { if (lowerCoin === 'firo' || lowerCoin === 'zcoin') {
@ -2059,16 +2060,16 @@ function createCombinedRateTooltip(offer, coinFrom, coinTo, isSentOffers, treatA
const fromSymbol = getPriceKey(coinFrom); const fromSymbol = getPriceKey(coinFrom);
const toSymbol = getPriceKey(coinTo); const toSymbol = getPriceKey(coinTo);
const fromPriceUSD = latestPrices[fromSymbol]?.usd || 0; const fromPriceUSD = latestPrices[fromSymbol]?.usd || 0;
const toPriceUSD = latestPrices[toSymbol]?.usd || 0; const toPriceUSD = latestPrices[toSymbol]?.usd || 0;
const rateInUSD = rate * toPriceUSD; const rateInUSD = rate * toPriceUSD;
const marketRate = fromPriceUSD / toPriceUSD; const marketRate = fromPriceUSD / toPriceUSD;
const percentDiff = ((rate - marketRate) / marketRate) * 100; const percentDiff = ((rate - marketRate) / marketRate) * 100;
const formattedPercentDiff = percentDiff.toFixed(2); const formattedPercentDiff = percentDiff.toFixed(2);
const percentDiffDisplay = formattedPercentDiff === "0.00" ? "0.00" : const percentDiffDisplay = formattedPercentDiff === "0.00" ? "0.00" :
(percentDiff > 0 ? `+${formattedPercentDiff}` : formattedPercentDiff); (percentDiff > 0 ? `+${formattedPercentDiff}` : formattedPercentDiff);
const aboveOrBelow = percentDiff > 0 ? "above" : percentDiff < 0 ? "below" : "at"; const aboveOrBelow = percentDiff > 0 ? "above" : percentDiff < 0 ? "below" : "at";
@ -2166,7 +2167,7 @@ function hasActiveFilters() {
const selectElements = filterForm.querySelectorAll('select'); const selectElements = filterForm.querySelectorAll('select');
let hasChangedFilters = false; let hasChangedFilters = false;
selectElements.forEach(select => { selectElements.forEach(select => {
if (select.value !== 'any') { if (select.value !== 'any') {
hasChangedFilters = true; hasChangedFilters = true;
@ -2205,13 +2206,13 @@ function getCoinSymbolLowercase(coin) {
function coinMatches(offerCoin, filterCoin) { function coinMatches(offerCoin, filterCoin) {
if (!offerCoin || !filterCoin) return false; if (!offerCoin || !filterCoin) return false;
offerCoin = offerCoin.toLowerCase(); offerCoin = offerCoin.toLowerCase();
filterCoin = filterCoin.toLowerCase(); filterCoin = filterCoin.toLowerCase();
if (offerCoin === filterCoin) return true; if (offerCoin === filterCoin) return true;
if ((offerCoin === 'firo' || offerCoin === 'zcoin') && if ((offerCoin === 'firo' || offerCoin === 'zcoin') &&
(filterCoin === 'firo' || filterCoin === 'zcoin')) { (filterCoin === 'firo' || filterCoin === 'zcoin')) {
return true; return true;
} }
@ -2229,7 +2230,7 @@ function coinMatches(offerCoin, filterCoin) {
if (particlVariants.includes(filterCoin)) { if (particlVariants.includes(filterCoin)) {
return offerCoin === filterCoin; return offerCoin === filterCoin;
} }
return false; return false;
} }
@ -2259,7 +2260,7 @@ function getTimeUntilNextExpiration() {
const timeUntilExpiration = offer.expire_at - currentTime; const timeUntilExpiration = offer.expire_at - currentTime;
return timeUntilExpiration > 0 && timeUntilExpiration < earliest ? timeUntilExpiration : earliest; return timeUntilExpiration > 0 && timeUntilExpiration < earliest ? timeUntilExpiration : earliest;
}, Infinity); }, Infinity);
return Math.max(MIN_REFRESH_INTERVAL, Math.min(nextExpiration, 300)); return Math.max(MIN_REFRESH_INTERVAL, Math.min(nextExpiration, 300));
} }
@ -2270,7 +2271,7 @@ function calculateInverseRate(rate) {
function formatTime(timestamp, addAgoSuffix = false) { function formatTime(timestamp, addAgoSuffix = false) {
const now = Math.floor(Date.now() / 1000); const now = Math.floor(Date.now() / 1000);
const diff = Math.abs(now - timestamp); const diff = Math.abs(now - timestamp);
let timeString; let timeString;
if (diff < 60) { if (diff < 60) {
timeString = `${diff} seconds`; timeString = `${diff} seconds`;
@ -2308,7 +2309,7 @@ function getCoinSymbol(fullName) {
'Particl': 'PART', 'Particl Blind': 'PART', 'Particl Anon': 'PART', 'Particl': 'PART', 'Particl Blind': 'PART', 'Particl Anon': 'PART',
'PIVX': 'PIVX', 'Firo': 'FIRO', 'Zcoin': 'FIRO', 'PIVX': 'PIVX', 'Firo': 'FIRO', 'Zcoin': 'FIRO',
'Dash': 'DASH', 'Decred': 'DCR', 'Wownero': 'WOW', 'Dash': 'DASH', 'Decred': 'DCR', 'Wownero': 'WOW',
'Bitcoin Cash': 'BCH' 'Bitcoin Cash': 'BCH', 'Dogecoin': 'DOGE'
}; };
return symbolMap[fullName] || fullName; return symbolMap[fullName] || fullName;
} }
@ -2317,7 +2318,7 @@ function getCoinSymbol(fullName) {
document.querySelectorAll('th[data-sortable="true"]').forEach(header => { document.querySelectorAll('th[data-sortable="true"]').forEach(header => {
header.addEventListener('click', () => { header.addEventListener('click', () => {
const columnIndex = parseInt(header.getAttribute('data-column-index')); const columnIndex = parseInt(header.getAttribute('data-column-index'));
if (currentSortColumn === columnIndex) { if (currentSortColumn === columnIndex) {
currentSortDirection = currentSortDirection === 'asc' ? 'desc' : 'asc'; currentSortDirection = currentSortDirection === 'asc' ? 'desc' : 'asc';
} else { } else {
@ -2348,7 +2349,7 @@ document.querySelectorAll('th[data-sortable="true"]').forEach(header => {
localStorage.setItem('tableSortColumn', currentSortColumn); localStorage.setItem('tableSortColumn', currentSortColumn);
localStorage.setItem('tableSortDirection', currentSortDirection); localStorage.setItem('tableSortDirection', currentSortDirection);
applyFilters(); applyFilters();
}); });
@ -2357,19 +2358,19 @@ document.querySelectorAll('th[data-sortable="true"]').forEach(header => {
const eventListeners = { const eventListeners = {
listeners: [], listeners: [],
add(element, eventType, handler, options = false) { add(element, eventType, handler, options = false) {
element.addEventListener(eventType, handler, options); element.addEventListener(eventType, handler, options);
this.listeners.push({ element, eventType, handler, options }); this.listeners.push({ element, eventType, handler, options });
// console.log(`Added ${eventType} listener to`, element); // console.log(`Added ${eventType} listener to`, element);
}, },
addWindowListener(eventType, handler, options = false) { addWindowListener(eventType, handler, options = false) {
window.addEventListener(eventType, handler, options); window.addEventListener(eventType, handler, options);
this.listeners.push({ element: window, eventType, handler, options }); this.listeners.push({ element: window, eventType, handler, options });
// console.log(`Added ${eventType} window listener`); // console.log(`Added ${eventType} window listener`);
}, },
removeAll() { removeAll() {
console.log('Removing all event listeners...'); console.log('Removing all event listeners...');
this.listeners.forEach(({ element, eventType, handler, options }) => { this.listeners.forEach(({ element, eventType, handler, options }) => {
@ -2378,7 +2379,7 @@ const eventListeners = {
}); });
this.listeners = []; this.listeners = [];
}, },
removeByElement(element) { removeByElement(element) {
const remainingListeners = []; const remainingListeners = [];
this.listeners = this.listeners.filter(listener => { this.listeners = this.listeners.filter(listener => {
@ -2400,29 +2401,29 @@ const eventListeners = {
const timerManager = { const timerManager = {
intervals: [], intervals: [],
timeouts: [], timeouts: [],
addInterval(callback, delay) { addInterval(callback, delay) {
const intervalId = setInterval(callback, delay); const intervalId = setInterval(callback, delay);
this.intervals.push(intervalId); this.intervals.push(intervalId);
return intervalId; return intervalId;
}, },
addTimeout(callback, delay) { addTimeout(callback, delay) {
const timeoutId = setTimeout(callback, delay); const timeoutId = setTimeout(callback, delay);
this.timeouts.push(timeoutId); this.timeouts.push(timeoutId);
return timeoutId; return timeoutId;
}, },
clearAllIntervals() { clearAllIntervals() {
this.intervals.forEach(clearInterval); this.intervals.forEach(clearInterval);
this.intervals = []; this.intervals = [];
}, },
clearAllTimeouts() { clearAllTimeouts() {
this.timeouts.forEach(clearTimeout); this.timeouts.forEach(clearTimeout);
this.timeouts = []; this.timeouts = [];
}, },
clearAll() { clearAll() {
this.clearAllIntervals(); this.clearAllIntervals();
this.clearAllTimeouts(); this.clearAllTimeouts();
@ -2435,7 +2436,7 @@ document.addEventListener('DOMContentLoaded', () => {
console.log('View type:', isSentOffers ? 'sent offers' : 'received offers'); console.log('View type:', isSentOffers ? 'sent offers' : 'received offers');
updateClearFiltersButton(); updateClearFiltersButton();
// Add event listeners for filter controls // Add event listeners for filter controls
const selectElements = filterForm.querySelectorAll('select'); const selectElements = filterForm.querySelectorAll('select');
selectElements.forEach(select => { selectElements.forEach(select => {
@ -2443,7 +2444,7 @@ document.addEventListener('DOMContentLoaded', () => {
updateClearFiltersButton(); updateClearFiltersButton();
}); });
}); });
filterForm.addEventListener('change', () => { filterForm.addEventListener('change', () => {
applyFilters(); applyFilters();
updateClearFiltersButton(); updateClearFiltersButton();
@ -2506,7 +2507,7 @@ document.addEventListener('DOMContentLoaded', () => {
eventListeners.add(document.getElementById('refreshOffers'), 'click', async () => { eventListeners.add(document.getElementById('refreshOffers'), 'click', async () => {
console.log('Manual refresh initiated'); console.log('Manual refresh initiated');
const refreshButton = document.getElementById('refreshOffers'); const refreshButton = document.getElementById('refreshOffers');
const refreshIcon = document.getElementById('refreshIcon'); const refreshIcon = document.getElementById('refreshIcon');
const refreshText = document.getElementById('refreshText'); const refreshText = document.getElementById('refreshText');
@ -2529,13 +2530,13 @@ document.addEventListener('DOMContentLoaded', () => {
jsonData = formatInitialData(processedNewData); jsonData = formatInitialData(processedNewData);
originalJsonData = [...jsonData]; originalJsonData = [...jsonData];
await updateOffersTable(); await updateOffersTable();
updateJsonView(); updateJsonView();
updatePaginationInfo(); updatePaginationInfo();
console.log(' Manual refresh completed successfully'); console.log(' Manual refresh completed successfully');
} catch (error) { } catch (error) {
console.error('Error during manual refresh:', error); console.error('Error during manual refresh:', error);
ui.displayErrorMessage('Failed to refresh offers. Please try again later.'); ui.displayErrorMessage('Failed to refresh offers. Please try again later.');