fix: Post-purchase holder dashboard on landing page (#150)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
af10dcf4c6
commit
fad6486152
5 changed files with 203 additions and 49 deletions
65
packages/ui-shared/src/composables/useEthPrice.ts
Normal file
65
packages/ui-shared/src/composables/useEthPrice.ts
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
import { ref, onMounted, onUnmounted } from 'vue';
|
||||
|
||||
const ETH_PRICE_CACHE_MS = 5 * 60 * 1000; // 5 minutes
|
||||
|
||||
// Module-level cache shared across all composable instances on the same page
|
||||
let _cachedPrice: number | null = null;
|
||||
let _cacheTime = 0;
|
||||
|
||||
export function formatUsd(usd: number): string {
|
||||
if (usd >= 1000) return `$${(usd / 1000).toFixed(1)}k`;
|
||||
if (usd >= 1) return `$${usd.toFixed(2)}`;
|
||||
if (usd >= 0.01) return `$${usd.toFixed(3)}`;
|
||||
return `$${usd.toFixed(4)}`;
|
||||
}
|
||||
|
||||
export function formatEthCompact(eth: number): string {
|
||||
if (eth === 0) return '0 ETH';
|
||||
if (eth >= 1) return `${eth.toFixed(2)} ETH`;
|
||||
if (eth >= 0.01) return `${eth.toFixed(4)} ETH`;
|
||||
if (eth >= 0.0001) return `${eth.toFixed(6)} ETH`;
|
||||
return `${eth.toPrecision(4)} ETH`;
|
||||
}
|
||||
|
||||
export function useEthPrice() {
|
||||
const ethUsdPrice = ref<number | null>(_cachedPrice);
|
||||
|
||||
async function fetchEthPrice() {
|
||||
const now = Date.now();
|
||||
if (_cachedPrice !== null && now - _cacheTime < ETH_PRICE_CACHE_MS) {
|
||||
ethUsdPrice.value = _cachedPrice;
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const controller = new AbortController();
|
||||
const timeout = setTimeout(() => controller.abort(), 5000);
|
||||
const resp = await fetch(
|
||||
'https://api.coingecko.com/api/v3/simple/price?ids=ethereum&vs_currencies=usd',
|
||||
{ signal: controller.signal },
|
||||
);
|
||||
clearTimeout(timeout);
|
||||
if (!resp.ok) throw new Error('ETH price fetch failed');
|
||||
const data = await resp.json();
|
||||
if (data.ethereum?.usd) {
|
||||
_cachedPrice = data.ethereum.usd;
|
||||
_cacheTime = now;
|
||||
ethUsdPrice.value = _cachedPrice;
|
||||
}
|
||||
} catch {
|
||||
// Keep existing cached price or null; ETH fallback will be used
|
||||
}
|
||||
}
|
||||
|
||||
let interval: ReturnType<typeof setInterval> | null = null;
|
||||
|
||||
onMounted(async () => {
|
||||
await fetchEthPrice();
|
||||
interval = setInterval(() => void fetchEthPrice(), ETH_PRICE_CACHE_MS);
|
||||
});
|
||||
|
||||
onUnmounted(() => {
|
||||
if (interval) clearInterval(interval);
|
||||
});
|
||||
|
||||
return { ethUsdPrice, fetchEthPrice };
|
||||
}
|
||||
|
|
@ -5,8 +5,8 @@ const POLL_INTERVAL_MS = 30_000;
|
|||
function formatTokenAmount(rawWei: string, decimals = 18): number {
|
||||
try {
|
||||
const big = BigInt(rawWei);
|
||||
const divisor = 10 ** decimals;
|
||||
return Number(big) / divisor;
|
||||
// Use BigInt arithmetic to avoid float64 precision loss at high values
|
||||
return Number(big * 10000n / (10n ** BigInt(decimals))) / 10000;
|
||||
} catch {
|
||||
return 0;
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue