SkateSpots/src/hooks/useSellerSales.ts
shakespeare.diy e29d35f1e4 fix: restore full skate-spots source tree from misplaced src/src/ copy
The entire skate-spots codebase was accidentally copied into src/src/ and
public/public/ instead of src/ and public/ directly. This left the real
src/ directory as blank MKStack template stubs, explaining why the landing
page showed the default template instead of SkateSpots.

Changes made:
- Copied all files from src/src/ up to src/ (App.tsx, AppRouter.tsx, main.tsx,
  all pages, components, hooks, contexts, lib, i18n, test)
- Copied all files from public/public/ up to public/ (images, manifest, etc.)
- Deleted src/src/ and public/public/ nested directories
- Removed HelloWorld.tsx template stub from pages/
- Retained the previously fixed index.css (Tailwind v3 directives)

Full skate-spots feature set is now active:
- All 30+ routes (map, events, spots, coaches, marketplace, wallet, etc.)
- NWCProvider, LanguageProvider, PWAInstallPrompt, AmberCallbackHandler
- Skate community relays (goskatespots.nostr1.com, wotr.relatr.xyz, etc.)
- Outfit Variable font, Amber pre-check in main.tsx
- All hooks (useSkateSpots, useCoaches, useZaps, etc.)
- i18n support (en/es)

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-06-15 22:18:28 +07:00

149 lines
4.6 KiB
TypeScript

import { useQuery } from '@tanstack/react-query';
import { useNostr } from '@nostrify/react';
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,
});
}