SkateSpots/src/hooks/useSellerSales.ts
SkateSpots d1f8870379 chore(lint): remove all explicit any + sweep dead imports
- Fix all 12 no-explicit-any (NPool, NostrEvent[], LabProfile, structural
  Cluster type, explicit SkateSpot cast) -> 0 remaining
- Remove unused imports/vars across 48 files (manual + a verified codemod for
  unused import specifiers only); tsc + tests + boot smoke all green
- Warning backlog: 182 -> 76 (remaining: ~55 no-unused-vars locals,
  14 react-hooks/exhaustive-deps, 7 react-refresh)
- AGENTS.md baseline updated
2026-09-26 21:40:42 +07:00

149 lines
4.6 KiB
TypeScript

import { useQuery } from '@tanstack/react-query';
import { useSkateRelays } from '@/hooks/useSkateRelays';
/**
* Hook to track completed sales for a seller.
*
* Sales are tracked via status changes in marketplace listings (kind 30402).
* A "sale" is counted when a listing has status = 'sold'.
*
* Future enhancement: Could also track via kind 1111 comments with specific tags
* to confirm receipt/completion of sales.
*/
export function useSellerSales(pubkey: string | undefined) {
const { querySkateContent, getQueryKeySuffix } = useSkateRelays();
return useQuery({
queryKey: ['seller-sales', pubkey, getQueryKeySuffix()],
queryFn: async () => {
if (!pubkey) return { totalSales: 0, recentSales: [] };
const signal = AbortSignal.timeout(8000);
try {
// Query all marketplace listings by this seller
const events = await querySkateContent(
[
{
kinds: [30402], // NIP-99 Classified Listings
authors: [pubkey],
'#t': ['skateboarding'],
limit: 200,
},
],
{ signal }
);
// Deduplicate by d-tag (keep latest version of each listing)
const listingMap = new Map<string, typeof events[0]>();
for (const event of events) {
const d = event.tags.find(([name]) => name === 'd')?.[1];
if (d) {
const existing = listingMap.get(d);
if (!existing || event.created_at > existing.created_at) {
listingMap.set(d, event);
}
}
}
// Count listings with status = 'sold'
const allListings = Array.from(listingMap.values());
const soldListings = allListings.filter(event => {
const status = event.tags.find(([name]) => name === 'status')?.[1];
return status === 'sold';
});
// Get recent sales (last 5)
const recentSales = soldListings
.sort((a, b) => b.created_at - a.created_at)
.slice(0, 5)
.map(event => ({
id: event.id,
title: event.tags.find(([name]) => name === 'title')?.[1] || 'Unknown item',
soldAt: event.created_at,
}));
return {
totalSales: soldListings.length,
recentSales,
};
} catch (error) {
console.warn('[useSellerSales] Error:', error);
return { totalSales: 0, recentSales: [] };
}
},
enabled: !!pubkey,
staleTime: 300000, // 5 minutes
gcTime: 600000, // 10 minutes
});
}
/**
* Batch hook to get sales for multiple sellers at once
*/
export function useBatchSellerSales(pubkeys: string[]) {
const { querySkateContent, getQueryKeySuffix } = useSkateRelays();
return useQuery({
queryKey: ['batch-seller-sales', pubkeys.sort().join(','), getQueryKeySuffix()],
queryFn: async () => {
if (pubkeys.length === 0) return new Map<string, number>();
const signal = AbortSignal.timeout(10000);
try {
// Query listings for all sellers at once
const events = await querySkateContent(
[
{
kinds: [30402],
authors: pubkeys,
'#t': ['skateboarding'],
limit: 500,
},
],
{ signal }
);
// Group by pubkey and count sold items
const salesByPubkey = new Map<string, number>();
// Initialize all pubkeys with 0
for (const pk of pubkeys) {
salesByPubkey.set(pk, 0);
}
// Deduplicate by pubkey+d-tag (keep latest version of each listing)
const listingMap = new Map<string, typeof events[0]>();
for (const event of events) {
const d = event.tags.find(([name]) => name === 'd')?.[1];
if (d) {
const key = `${event.pubkey}:${d}`;
const existing = listingMap.get(key);
if (!existing || event.created_at > existing.created_at) {
listingMap.set(key, event);
}
}
}
// Count sold listings per pubkey
for (const event of listingMap.values()) {
const status = event.tags.find(([name]) => name === 'status')?.[1];
if (status === 'sold') {
const current = salesByPubkey.get(event.pubkey) || 0;
salesByPubkey.set(event.pubkey, current + 1);
}
}
return salesByPubkey;
} catch (error) {
console.warn('[useBatchSellerSales] Error:', error);
return new Map<string, number>();
}
},
enabled: pubkeys.length > 0,
staleTime: 300000,
gcTime: 600000,
});
}