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.
121 lines
3.6 KiB
TypeScript
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';
|
|
}
|