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>
144 lines
3.8 KiB
TypeScript
144 lines
3.8 KiB
TypeScript
/**
|
|
* Skateboard emoji rating system for SkateSpots
|
|
* Uses 🛹 emojis instead of stars, from 1 to 10
|
|
*/
|
|
|
|
/**
|
|
* Generate skateboard rating display (1-10 skateboards based on count)
|
|
* @param count - The count to convert to a rating (sales, reviews, etc.)
|
|
* @param maxDisplay - Maximum skateboards to display (default 10)
|
|
* @returns Object with emoji string and numeric rating
|
|
*/
|
|
export function getSkateboardRating(count: number, maxDisplay = 10): {
|
|
rating: number;
|
|
display: string;
|
|
label: string;
|
|
} {
|
|
// Scale: 1-10 based on count thresholds
|
|
let rating: number;
|
|
let label: string;
|
|
|
|
if (count === 0) {
|
|
rating = 0;
|
|
label = 'New';
|
|
} else if (count === 1) {
|
|
rating = 1;
|
|
label = 'Just starting';
|
|
} else if (count <= 3) {
|
|
rating = 2;
|
|
label = 'Getting known';
|
|
} else if (count <= 5) {
|
|
rating = 3;
|
|
label = 'Building rep';
|
|
} else if (count <= 8) {
|
|
rating = 4;
|
|
label = 'Solid';
|
|
} else if (count <= 12) {
|
|
rating = 5;
|
|
label = 'Reliable';
|
|
} else if (count <= 18) {
|
|
rating = 6;
|
|
label = 'Well known';
|
|
} else if (count <= 25) {
|
|
rating = 7;
|
|
label = 'Established';
|
|
} else if (count <= 35) {
|
|
rating = 8;
|
|
label = 'Trusted';
|
|
} else if (count <= 50) {
|
|
rating = 9;
|
|
label = 'Pro seller';
|
|
} else {
|
|
rating = 10;
|
|
label = 'Legendary';
|
|
}
|
|
|
|
// Clamp to max display
|
|
const displayRating = Math.min(rating, maxDisplay);
|
|
const display = '🛹'.repeat(displayRating);
|
|
|
|
return { rating, display, label };
|
|
}
|
|
|
|
/**
|
|
* Get skateboard rating for skate spots (based on reviews/ratings)
|
|
* @param reviewCount - Number of reviews
|
|
* @param averageRating - Average rating (1-5 scale typically)
|
|
* @returns Object with emoji string and numeric rating
|
|
*/
|
|
export function getSpotSkateboardRating(reviewCount: number, averageRating: number = 0): {
|
|
rating: number;
|
|
display: string;
|
|
label: string;
|
|
} {
|
|
// Combine review count and average rating for final score
|
|
// Weight: 60% average rating, 40% review count factor
|
|
let rating: number;
|
|
let label: string;
|
|
|
|
if (reviewCount === 0) {
|
|
rating = 0;
|
|
label = 'No reviews';
|
|
} else {
|
|
// Scale average rating (1-5) to (2-10)
|
|
const ratingScore = ((averageRating - 1) / 4) * 8 + 2;
|
|
|
|
// Review count bonus (up to +2 for 20+ reviews)
|
|
const countBonus = Math.min(reviewCount / 10, 2);
|
|
|
|
rating = Math.round(Math.min(ratingScore + countBonus, 10));
|
|
|
|
if (rating <= 2) label = 'Sketchy';
|
|
else if (rating <= 4) label = 'Decent';
|
|
else if (rating <= 6) label = 'Solid spot';
|
|
else if (rating <= 8) label = 'Classic';
|
|
else label = 'Legendary';
|
|
}
|
|
|
|
const display = rating > 0 ? '🛹'.repeat(rating) : '';
|
|
|
|
return { rating, display, label };
|
|
}
|
|
|
|
/**
|
|
* Compact display for tight spaces (shows count + single emoji)
|
|
*/
|
|
export function getCompactRating(count: number): string {
|
|
if (count === 0) return '🛹 0';
|
|
return `🛹 ${count}`;
|
|
}
|
|
|
|
/**
|
|
* Trust badge configurations matching the skateboard theme
|
|
*/
|
|
export const SELLER_TRUST_BADGES = {
|
|
verified: {
|
|
emoji: '✅',
|
|
label: 'Verified',
|
|
description: 'You follow them directly',
|
|
className: 'border-green-500/50 text-green-400 bg-green-500/10'
|
|
},
|
|
trusted: {
|
|
emoji: '🛡️',
|
|
label: 'Trusted',
|
|
description: 'Friend-of-friend or NIP-05',
|
|
className: 'border-blue-500/50 text-blue-400 bg-blue-500/10'
|
|
},
|
|
new: {
|
|
emoji: '⚠️',
|
|
label: 'New',
|
|
description: 'No trust signals yet',
|
|
className: 'border-yellow-500/50 text-yellow-400 bg-yellow-500/10'
|
|
},
|
|
} as const;
|
|
|
|
export type TrustLevel = keyof typeof SELLER_TRUST_BADGES;
|
|
|
|
/**
|
|
* Get trust level from trust score
|
|
*/
|
|
export function getTrustLevel(trustScore: number | undefined): TrustLevel {
|
|
if (trustScore === undefined || trustScore < 50) return 'new';
|
|
if (trustScore >= 90) return 'verified';
|
|
return 'trusted';
|
|
}
|