SkateSpots/src/hooks/useBTCMap.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

139 lines
3.9 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;
};
}
interface BTCMapResponse {
type: string;
features: Array<{
type: string;
geometry: {
type: string;
coordinates: [number, number]; // [lon, lat]
};
properties: {
id: string;
name?: string;
'payment:lightning'?: string;
'payment:onchain'?: string;
[key: string]: string | undefined;
};
}>;
}
/**
* 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';
}