SkateSpots/src/components/SortDropdown.tsx
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

148 lines
3.8 KiB
TypeScript

import { ArrowUpDown, Clock, TrendingUp, MapPin, MessageCircle, Zap, Shield } from 'lucide-react';
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '@/components/ui/select';
import { cn } from '@/lib/utils';
export type SortOption =
| 'recent'
| 'trust'
| 'proximity'
| 'popular'
| 'zaps';
interface SortDropdownProps {
value: SortOption;
onChange: (value: SortOption) => void;
className?: string;
showProximity?: boolean; // Only show if geolocation available
contentType?: 'spots' | 'events' | 'coaches' | 'generic';
}
const SORT_OPTIONS = {
recent: {
label: 'Most Recent',
icon: Clock,
description: 'Newest first',
},
trust: {
label: 'Highest Trust',
icon: Shield,
description: 'Most trusted creators',
},
proximity: {
label: 'Closest to Me',
icon: MapPin,
description: 'Nearest locations',
},
popular: {
label: 'Most Popular',
icon: TrendingUp,
description: 'Most comments/reactions',
},
zaps: {
label: 'Most Zapped',
icon: Zap,
description: 'Highest value received',
},
};
export function SortDropdown({
value,
onChange,
className,
showProximity = false,
contentType = 'generic',
}: SortDropdownProps) {
const selectedOption = SORT_OPTIONS[value];
const Icon = selectedOption.icon;
// Determine which sort options to show based on content type
const availableOptions: SortOption[] = ['recent', 'trust'];
if (showProximity) {
availableOptions.push('proximity');
}
// Popular sorting available for all content types
availableOptions.push('popular');
// Zaps available for all content
availableOptions.push('zaps');
return (
<Select value={value} onValueChange={(v) => onChange(v as SortOption)}>
<SelectTrigger className={cn("w-[200px] gap-2", className)}>
<ArrowUpDown className="h-4 w-4 text-muted-foreground" />
<SelectValue>
<span className="flex items-center gap-2">
<Icon className="h-4 w-4" />
{selectedOption.label}
</span>
</SelectValue>
</SelectTrigger>
<SelectContent>
{availableOptions.map((option) => {
const opt = SORT_OPTIONS[option];
const OptionIcon = opt.icon;
return (
<SelectItem key={option} value={option}>
<div className="flex items-center gap-2 w-full">
<OptionIcon className="h-4 w-4 text-muted-foreground" />
<div className="flex-1">
<div className="font-medium">{opt.label}</div>
<div className="text-xs text-muted-foreground">{opt.description}</div>
</div>
</div>
</SelectItem>
);
})}
</SelectContent>
</Select>
);
}
/**
* Helper function to calculate popularity score for sorting
* Based on comments, reactions, and other engagement metrics
*/
export function calculatePopularity(
commentCount: number = 0,
reactionCount: number = 0,
zapCount: number = 0
): number {
// Weighted popularity: comments worth 3x, reactions 1x, zaps 5x
return (commentCount * 3) + (reactionCount * 1) + (zapCount * 5);
}
/**
* Calculate distance between two coordinates in kilometers
* Uses Haversine formula
*/
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);
}