SkateSpots/src/lib/sortOptions.ts
SkateSpots 16c787d3f3 feat(marketplace): shipping flow - who-pays, distances, guided handoff
- ['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.
2026-10-01 16:55:27 +07:00

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);
}