fix: address review findings for snatch notifications (#151)
- Extract relativeTime and formatTokenAmount to helper.ts, eliminating duplicated logic between CollapseHistory and NotificationBell - Use formatUnits (via formatTokenAmount) instead of Number(BigInt)/1e18 to avoid precision loss on large token amounts - Fix allRecentSnatches emptying after mark-seen: now runs two parallel queries — one filtered by lastSeen timestamp (unseen badge count) and one unfiltered (panel history), so history is preserved after opening - Remove dead no-op watch block from useSnatchNotifications Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
60d0859eb3
commit
2fffd2a567
4 changed files with 78 additions and 92 deletions
|
|
@ -1,4 +1,4 @@
|
|||
import { ref, computed, onMounted, onUnmounted, watch } from 'vue';
|
||||
import { ref, computed, onMounted, onUnmounted } from 'vue';
|
||||
import axios from 'axios';
|
||||
import { getAccount, watchAccount } from '@wagmi/core';
|
||||
import type { WatchAccountReturnType } from '@wagmi/core';
|
||||
|
|
@ -38,8 +38,45 @@ function setLastSeen(address: string, timestamp: string): void {
|
|||
localStorage.setItem(storageKey(address), timestamp);
|
||||
}
|
||||
|
||||
async function fetchUnseenSnatches(chainId: number, address: string): Promise<void> {
|
||||
const since = getLastSeen(address);
|
||||
async function fetchSnatchEvents(endpoint: string, address: string, since: string | null): Promise<SnatchEvent[]> {
|
||||
const holder = address.toLowerCase();
|
||||
const whereClause =
|
||||
since !== null
|
||||
? `{ holder: "${holder}", type: "snatch_out", timestamp_gt: "${since}" }`
|
||||
: `{ holder: "${holder}", type: "snatch_out" }`;
|
||||
const res = await axios.post(
|
||||
endpoint,
|
||||
{
|
||||
query: `query SnatchEvents {
|
||||
transactionss(
|
||||
where: ${whereClause}
|
||||
orderBy: "timestamp"
|
||||
orderDirection: "desc"
|
||||
limit: 20
|
||||
) {
|
||||
items { id timestamp tokenAmount ethAmount txHash }
|
||||
}
|
||||
}`,
|
||||
},
|
||||
{ timeout: GRAPHQL_TIMEOUT_MS }
|
||||
);
|
||||
|
||||
const errors = res.data?.errors;
|
||||
if (Array.isArray(errors) && errors.length > 0) {
|
||||
logger.info('useSnatchNotifications GraphQL errors', errors);
|
||||
return [];
|
||||
}
|
||||
return (res.data?.data?.transactionss?.items ?? []) as SnatchEvent[];
|
||||
}
|
||||
|
||||
async function refresh(chainId: number): Promise<void> {
|
||||
const account = getAccount(config as Config);
|
||||
if (!account.address) {
|
||||
unseenSnatches.value = [];
|
||||
allRecentSnatches.value = [];
|
||||
return;
|
||||
}
|
||||
|
||||
let endpoint: string;
|
||||
try {
|
||||
endpoint = resolveGraphqlEndpoint(chainId);
|
||||
|
|
@ -50,57 +87,28 @@ async function fetchUnseenSnatches(chainId: number, address: string): Promise<vo
|
|||
}
|
||||
|
||||
try {
|
||||
const res = await axios.post(
|
||||
endpoint,
|
||||
{
|
||||
query: `query UnseenSnatches($holder: String!, $since: BigInt!) {
|
||||
transactionss(
|
||||
where: { holder: $holder, type: "snatch_out", timestamp_gt: $since }
|
||||
orderBy: "timestamp"
|
||||
orderDirection: "desc"
|
||||
limit: 20
|
||||
) {
|
||||
items { id timestamp tokenAmount ethAmount txHash }
|
||||
}
|
||||
}`,
|
||||
variables: { holder: address.toLowerCase(), since },
|
||||
},
|
||||
{ timeout: GRAPHQL_TIMEOUT_MS }
|
||||
);
|
||||
|
||||
const errors = res.data?.errors;
|
||||
if (Array.isArray(errors) && errors.length > 0) {
|
||||
logger.info('useSnatchNotifications GraphQL errors', errors);
|
||||
return;
|
||||
}
|
||||
|
||||
const items: SnatchEvent[] = res.data?.data?.transactionss?.items ?? [];
|
||||
unseenSnatches.value = items;
|
||||
allRecentSnatches.value = items;
|
||||
const since = getLastSeen(account.address);
|
||||
const [unseen, all] = await Promise.all([
|
||||
fetchSnatchEvents(endpoint, account.address, since),
|
||||
fetchSnatchEvents(endpoint, account.address, null),
|
||||
]);
|
||||
unseenSnatches.value = unseen;
|
||||
allRecentSnatches.value = all;
|
||||
} catch (err) {
|
||||
logger.info('useSnatchNotifications fetch failed', err);
|
||||
}
|
||||
}
|
||||
|
||||
async function refresh(chainId: number): Promise<void> {
|
||||
const account = getAccount(config as Config);
|
||||
if (!account.address) {
|
||||
unseenSnatches.value = [];
|
||||
allRecentSnatches.value = [];
|
||||
return;
|
||||
}
|
||||
await fetchUnseenSnatches(chainId, account.address);
|
||||
}
|
||||
|
||||
function markSeen(): void {
|
||||
const account = getAccount(config as Config);
|
||||
if (!account.address || allRecentSnatches.value.length === 0) return;
|
||||
if (!account.address) return;
|
||||
|
||||
// Use the most recent timestamp from current results
|
||||
const latestTs = allRecentSnatches.value[0]?.timestamp;
|
||||
// Advance last-seen to the most recent event timestamp
|
||||
const latestTs = allRecentSnatches.value[0]?.timestamp ?? unseenSnatches.value[0]?.timestamp;
|
||||
if (latestTs) {
|
||||
setLastSeen(account.address, latestTs);
|
||||
}
|
||||
// Clear badge; allRecentSnatches stays populated so the panel still shows history
|
||||
unseenSnatches.value = [];
|
||||
}
|
||||
|
||||
|
|
@ -146,11 +154,6 @@ export function useSnatchNotifications(chainId: number = DEFAULT_CHAIN_ID) {
|
|||
}
|
||||
});
|
||||
|
||||
// Re-fetch when bell is closed to clear unseen after markSeen
|
||||
watch(isOpen, async newVal => {
|
||||
if (!newVal) return;
|
||||
});
|
||||
|
||||
return {
|
||||
unseenCount,
|
||||
unseenSnatches,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue