- ['shipping','buyer'|'seller'|'split'] tag + create-page selector. - Distance from the viewer's last known location (cache only, no GPS prompt) on both gear cards, and a 'Nearest (local)' sort on GearSwapPage. - HandoffHelper (buyer-only, in ListingTradeActions) opens the trade DM pre-filled: suggest a time & place / share delivery details / use a locker, with anti-scam copy. Details stay in the NIP-17 DM; the app never sees them. - ships deals now nudge safety on all platforms, not just when escrow is ready.
57 lines
1.6 KiB
TypeScript
57 lines
1.6 KiB
TypeScript
/**
|
|
* Sort/geo helpers shared across pages. Kept out of SortDropdown.tsx so that
|
|
* component file exports only a component (React Fast Refresh).
|
|
*/
|
|
import ngeohash from 'ngeohash';
|
|
|
|
interface LatLng {
|
|
lat: number;
|
|
lng: number;
|
|
}
|
|
|
|
/**
|
|
* Distance in km from `from` to a listing's own geohash, or null when the
|
|
* listing carries no location. Used for the marketplace "Near me" sort + badges.
|
|
*/
|
|
export function itemDistanceKm(item: { geohash?: string }, from: LatLng): number | null {
|
|
if (!item.geohash) return null;
|
|
try {
|
|
const { latitude, longitude } = ngeohash.decode(item.geohash);
|
|
if (!Number.isFinite(latitude) || !Number.isFinite(longitude)) return null;
|
|
return calculateDistance(from.lat, from.lng, latitude, longitude);
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
/** Weighted popularity score: comments 3x, reactions 1x, zaps 5x. */
|
|
export function calculatePopularity(
|
|
commentCount = 0,
|
|
reactionCount = 0,
|
|
zapCount = 0,
|
|
): number {
|
|
return commentCount * 3 + reactionCount * 1 + zapCount * 5;
|
|
}
|
|
|
|
/** Great-circle distance between two coordinates, in kilometres (Haversine). */
|
|
export function calculateDistance(
|
|
lat1: number,
|
|
lon1: number,
|
|
lat2: number,
|
|
lon2: number,
|
|
): number {
|
|
const R = 6371; // Earth's radius in km
|
|
const dLat = toRad(lat2 - lat1);
|
|
const dLon = toRad(lon2 - lon1);
|
|
|
|
const a =
|
|
Math.sin(dLat / 2) * Math.sin(dLat / 2) +
|
|
Math.cos(toRad(lat1)) * Math.cos(toRad(lat2)) * Math.sin(dLon / 2) * Math.sin(dLon / 2);
|
|
|
|
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
|
|
return R * c;
|
|
}
|
|
|
|
function toRad(degrees: number): number {
|
|
return degrees * (Math.PI / 180);
|
|
}
|