SkateSpots/src/hooks/useBTCMap.ts
SkateSpots 6def78fffe chore(lint): clear all no-unused-vars warnings (position-precise)
Removed/renamed the remaining 39 unused locals/dead code, file-by-file:
- unused destructured bindings/params (remove the binding, keep the call)
- unused single-line pure consts and dead functions/interfaces removed
- deleted the dead _FeedItem component and its now-unused imports
- amber _readChunks self-call fixed; SpotMap popup call site updated

Warnings: 182 -> 7 (only react-refresh/only-export-components remain).
no-explicit-any=0, exhaustive-deps=0, no-unused-vars=0.
Gate: 101 tests, 0 errors, boot smoke green. AGENTS.md baseline + guidance updated.
2026-09-26 22:20:21 +07:00

121 lines
3.6 KiB
TypeScript

import { useQuery } from '@tanstack/react-query';
export interface BTCMapMerchant {
id: string;
name: string;
lat: number;
lon: number;
tags: {
name?: string;
'payment:lightning'?: string;
'payment:onchain'?: string;
'payment:lightning_contactless'?: string;
amenity?: string;
shop?: string;
cuisine?: string;
opening_hours?: string;
website?: string;
phone?: string;
};
osm_json?: {
type: string;
id: number;
};
}
/**
* Fetch Bitcoin-accepting merchants from BTCMap API
* Uses bounding box to get merchants in the visible map area
*/
export function useBTCMap(bounds?: {
north: number;
south: number;
east: number;
west: number;
}, enabled: boolean = false) {
return useQuery({
queryKey: ['btcmap', bounds?.north, bounds?.south, bounds?.east, bounds?.west],
queryFn: async ({ signal }): Promise<BTCMapMerchant[]> => {
if (!bounds) return [];
// BTCMap API endpoint - get elements in bounding box
// Using their public API
const url = `https://api.btcmap.org/v2/elements?updated_since=2020-01-01&bounds=${bounds.south},${bounds.west},${bounds.north},${bounds.east}`;
const response = await fetch(url, {
signal,
headers: {
'Accept': 'application/json',
}
});
if (!response.ok) {
throw new Error(`BTCMap API error: ${response.status}`);
}
const data = await response.json();
// Transform the response to our format
const merchants: BTCMapMerchant[] = data
.filter((item: { 'osm_json': { lat?: number; lon?: number }; deleted_at?: string }) =>
item.osm_json?.lat && item.osm_json?.lon && !item.deleted_at
)
.map((item: {
id: string;
'osm_json': {
lat: number;
lon: number;
tags?: Record<string, string>;
type?: string;
id?: number;
}
}) => ({
id: item.id,
name: item.osm_json.tags?.name || 'Bitcoin Merchant',
lat: item.osm_json.lat,
lon: item.osm_json.lon,
tags: item.osm_json.tags || {},
osm_json: {
type: item.osm_json.type || 'node',
id: item.osm_json.id || 0,
}
}));
return merchants;
},
enabled: enabled && !!bounds,
staleTime: 5 * 60 * 1000, // 5 minutes
gcTime: 30 * 60 * 1000, // 30 minutes
});
}
/**
* Get a human-readable category for the merchant
*/
export function getMerchantCategory(merchant: BTCMapMerchant): string {
const { tags } = merchant;
if (tags.amenity === 'cafe' || tags.amenity === 'coffee') return '☕ Café';
if (tags.amenity === 'restaurant') return '🍽️ Restaurant';
if (tags.amenity === 'bar' || tags.amenity === 'pub') return '🍺 Bar';
if (tags.amenity === 'fast_food') return '🍔 Fast Food';
if (tags.shop === 'convenience') return '🏪 Convenience';
if (tags.shop === 'supermarket') return '🛒 Supermarket';
if (tags.shop === 'clothes') return '👕 Clothing';
if (tags.shop === 'sports') return '⚽ Sports';
if (tags.shop === 'electronics') return '📱 Electronics';
if (tags.shop) return '🏬 Shop';
if (tags.amenity === 'atm') return '🏧 ATM';
if (tags.amenity === 'bank') return '🏦 Bank';
return '₿ Bitcoin Accepted';
}
/**
* Check if merchant accepts Lightning
*/
export function acceptsLightning(merchant: BTCMapMerchant): boolean {
const { tags } = merchant;
return tags['payment:lightning'] === 'yes' ||
tags['payment:lightning_contactless'] === 'yes';
}