## 1. sanitizeUrl() (already existed, verified applied) - Applied to ShopDetailPage, CoachDetailPage with urlDomain() tooltip hints - Blocks javascript:, data:, vbscript: and all non-http(s) schemes - Returns null for invalid URLs — link simply doesn't render ## 2. Geohash validation in parsers - Added isValidGeohash() in types.ts using ngeohash.decode() - Min 4 chars (country-level precision rejected), max 12 chars - Rejects lat/lng outside Earth bounds - Rejects 0,0 "null island" — classic bot artifact - VPN/Tor unaffected: geohash comes from map pin, not user IP ## 3. Content length limits in all parsers - MAX_TITLE_LEN=100, MAX_DESCRIPTION_LEN=3000, MAX_LOCATION_LEN=200 - MIN_TITLE_LEN=2, MIN_GEOHASH_LEN=4 - Applied to parseSkateSpot, parseSkateShop, parseSkateCoach, parseSkateFilmer, parseSkateEditor, parseSkateEvent ## 4. Smart WoT default (already in codebase, verified correct) - Logged out / <5 follows → 'all' (don't punish new/anon users) - Logged in with ≥5 follows → 'trusted' (sort trusted first) - Soft mode: trusted content floats to top, nothing hidden - Active in MapPage and EventsPage ## 5. Trusted count stats in useGrowthStats - Added trustedSpots and trustedMappers to GrowthStats type - Heuristic: pubkeys with ≥2 published items = organic community member - CommunityStatsPage now shows "🛡 X from active accounts" below spot count - Purely additive — both numbers always visible ## 6. OfficialBadge component (already existed, wired up) - Added to SpotCard, EventCard, ShopDetailPage, CoachDetailPage - Only renders for FOUNDER_PUBKEY (20e7c953...) - Zero-friction: invisible for all other pubkeys - Impersonation attempts immediately visible ## 7. Leaflet marker clustering - Installed leaflet.markercluster v1.5.3 + @types - MarkerClusterGroup replaces direct marker.addTo(map) - Custom cluster icon: red/orange gradient circle with count - maxClusterRadius: 60px, spiderfyOnMaxZoom: true - MarkerCluster CSS loaded from CDN in index.html ## 8. DM phishing warning banner - PhishingWarningBanner component in DirectMessagesPage - One-time dismissible (localStorage key: dm-phishing-warning-dismissed) - Message: SkateSpots will never DM you asking for keys/payments - Uses useLocalStorage hook, Shield icon, X dismiss button Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
48 lines
1.7 KiB
TypeScript
48 lines
1.7 KiB
TypeScript
import { Crown } from 'lucide-react';
|
|
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
|
|
|
|
// The verified founder/admin pubkey — hard-coded so it cannot be spoofed
|
|
export const FOUNDER_PUBKEY = '20e7c953439e66f3882ab96fe0faa8ba246d6fb12c03b0cb4e2dc4623b8bcb21';
|
|
|
|
interface OfficialBadgeProps {
|
|
pubkey: string;
|
|
size?: 'sm' | 'md';
|
|
}
|
|
|
|
/**
|
|
* Shows a "⚡ Official" badge on content published by the verified founder pubkey.
|
|
* Makes impersonation immediately visible — a fake account won't have this badge.
|
|
*/
|
|
export function OfficialBadge({ pubkey, size = 'sm' }: OfficialBadgeProps) {
|
|
if (pubkey !== FOUNDER_PUBKEY) return null;
|
|
|
|
if (size === 'sm') {
|
|
return (
|
|
<Tooltip>
|
|
<TooltipTrigger asChild>
|
|
<span className="inline-flex items-center gap-0.5 px-1.5 py-0.5 rounded-full bg-yellow-500/15 border border-yellow-500/40 text-yellow-400 text-[10px] font-bold leading-none cursor-default select-none">
|
|
<Crown className="h-2.5 w-2.5" />
|
|
Official
|
|
</span>
|
|
</TooltipTrigger>
|
|
<TooltipContent side="top" className="text-xs">
|
|
Published by the verified SkateSpots founder
|
|
</TooltipContent>
|
|
</Tooltip>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<Tooltip>
|
|
<TooltipTrigger asChild>
|
|
<span className="inline-flex items-center gap-1 px-2.5 py-1 rounded-full bg-yellow-500/15 border border-yellow-500/40 text-yellow-400 text-xs font-bold cursor-default select-none">
|
|
<Crown className="h-3.5 w-3.5" />
|
|
Official SkateSpots
|
|
</span>
|
|
</TooltipTrigger>
|
|
<TooltipContent side="top" className="text-xs">
|
|
Published by the verified SkateSpots founder
|
|
</TooltipContent>
|
|
</Tooltip>
|
|
);
|
|
}
|