From 94a88b2ba05a89c61655946ecb9e0f10bb0e0bea Mon Sep 17 00:00:00 2001 From: "shakespeare.diy" Date: Thu, 27 Aug 2026 15:07:13 +0700 Subject: [PATCH] v1.2.1: Community stats now show ALL Lab crafts + count every craft type - useGrowthStats now queries and exposes every Lab craft individually: artists, graffiti, tattoo, trainers, musicians and judges (previously folded into an opaque "other" bucket with no visible breakdown). - CommunityStatsPage renders a card for all 11 Lab crafts, so the section matches what /lab actually lists instead of only showing 5 categories. - labTotal + the content-mix donut's "Lab" slice now sum every craft, keeping the "total" badge and chart consistent. Note on relay mismatch: the app dedupes addressable events (keeps newest per pubkey+kind+d) and merges across the community relay + the user's relays, so counts can legitimately differ from a raw single-relay browser that shows stale duplicate versions. Bump version to 1.2.1 (versionCode 10201). Co-authored-by: shakespeare.diy --- package.json | 2 +- src/hooks/useGrowthStats.ts | 41 ++++++++++++++++++++++++++++---- src/pages/CommunityStatsPage.tsx | 36 ++++++++++++++++++---------- 3 files changed, 61 insertions(+), 18 deletions(-) diff --git a/package.json b/package.json index 6eedbf8..3004b1c 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "skate-spots", "private": true, - "version": "1.2.0", + "version": "1.2.1", "type": "module", "homepage": "https://skate-spots.com", "scripts": { diff --git a/src/hooks/useGrowthStats.ts b/src/hooks/useGrowthStats.ts index f1a4d7c..f999611 100644 --- a/src/hooks/useGrowthStats.ts +++ b/src/hooks/useGrowthStats.ts @@ -14,6 +14,7 @@ import { SKATE_TATTOO_KIND, SKATE_TRAINER_KIND, SKATE_MUSICIAN_KIND, + SKATE_JUDGE_KIND, SKATE_TOUR_KIND, SKATE_CREW_KIND, } from '@/lib/types'; @@ -56,6 +57,13 @@ export interface GrowthStats { totalEditors: number; totalPhotographers: number; totalBuilders: number; + // Additional Lab crafts + totalArtists: number; + totalGraffiti: number; + totalTattoo: number; + totalTrainers: number; + totalMusicians: number; + totalJudges: number; totalTours: number; totalCrews: number; /** All unique pubkeys that have published any content */ @@ -135,7 +143,12 @@ export function useGrowthStats() { builderEvents, tourEvents, crewEvents, - otherLabEvents, + artistEvents, + graffitiEvents, + tattooEvents, + trainerEvents, + musicianEvents, + judgeEvents, ] = await Promise.all([ querySkateContent([{ kinds: [SKATE_SPOT_KIND], '#t': ['skatespot'], limit: 2000 }], { signal }).catch(() => []), querySkateContent([{ kinds: [SKATE_SHOP_KIND], limit: 2000 }], { signal }).catch(() => []), @@ -147,9 +160,14 @@ export function useGrowthStats() { querySkateContent([{ kinds: [SKATE_BUILDER_KIND], limit: 500 }], { signal }).catch(() => []), querySkateContent([{ kinds: [SKATE_TOUR_KIND], limit: 500 }], { signal }).catch(() => []), querySkateContent([{ kinds: [SKATE_CREW_KIND], limit: 500 }], { signal }).catch(() => []), - // New Lab crafts (artist, graffiti, tattoo, trainer, musician) — folded - // into community totals/mappers without their own chart series. - querySkateContent([{ kinds: [SKATE_ARTIST_KIND, SKATE_GRAFFITI_KIND, SKATE_TATTOO_KIND, SKATE_TRAINER_KIND, SKATE_MUSICIAN_KIND], limit: 1000 }], { signal }).catch(() => []), + // All other Lab crafts, each counted individually so the stats page can + // show a card per craft (matching what /lab lists). + querySkateContent([{ kinds: [SKATE_ARTIST_KIND], limit: 500 }], { signal }).catch(() => []), + querySkateContent([{ kinds: [SKATE_GRAFFITI_KIND], limit: 500 }], { signal }).catch(() => []), + querySkateContent([{ kinds: [SKATE_TATTOO_KIND], limit: 500 }], { signal }).catch(() => []), + querySkateContent([{ kinds: [SKATE_TRAINER_KIND], limit: 500 }], { signal }).catch(() => []), + querySkateContent([{ kinds: [SKATE_MUSICIAN_KIND], limit: 500 }], { signal }).catch(() => []), + querySkateContent([{ kinds: [SKATE_JUDGE_KIND], limit: 500 }], { signal }).catch(() => []), ]); // Deduplicate addressable events (keep newest per pubkey+kind+d) @@ -174,7 +192,14 @@ export function useGrowthStats() { const builders = dedup(builderEvents); const tours = dedup(tourEvents); const crews = dedup(crewEvents); - const otherLab = dedup(otherLabEvents); + const artists = dedup(artistEvents); + const graffiti = dedup(graffitiEvents); + const tattoo = dedup(tattooEvents); + const trainers = dedup(trainerEvents); + const musicians = dedup(musicianEvents); + const judges = dedup(judgeEvents); + // Aggregate of the "other" crafts for active-mapper / total rollups. + const otherLab = [...artists, ...graffiti, ...tattoo, ...trainers, ...musicians, ...judges]; // ── Monthly bucketing ─────────────────────────────────────────────── @@ -289,6 +314,12 @@ export function useGrowthStats() { totalEditors: editors.length, totalPhotographers: photographers.length, totalBuilders: builders.length, + totalArtists: artists.length, + totalGraffiti: graffiti.length, + totalTattoo: tattoo.length, + totalTrainers: trainers.length, + totalMusicians: musicians.length, + totalJudges: judges.length, totalTours: tours.length, totalCrews: crews.length, totalMappers: allPubkeys.size, diff --git a/src/pages/CommunityStatsPage.tsx b/src/pages/CommunityStatsPage.tsx index 197220e..f004c9c 100644 --- a/src/pages/CommunityStatsPage.tsx +++ b/src/pages/CommunityStatsPage.tsx @@ -25,10 +25,25 @@ import { Camera, Hammer, TrendingUp, Radio, Zap, ExternalLink, Trophy, FlaskConical, + Palette, SprayCan, PenTool, Dumbbell, Music, Gavel, } from 'lucide-react'; // ─── Helpers ───────────────────────────────────────────────────────────────── +/** Sum of every Lab craft count — keeps the "total" badge and donut in sync. */ +function labCraftTotal(s: { + totalCoaches: number; totalFilmers: number; totalEditors: number; + totalPhotographers: number; totalBuilders: number; totalArtists: number; + totalGraffiti: number; totalTattoo: number; totalTrainers: number; + totalMusicians: number; totalJudges: number; +}): number { + return ( + s.totalCoaches + s.totalFilmers + s.totalEditors + s.totalPhotographers + + s.totalBuilders + s.totalArtists + s.totalGraffiti + s.totalTattoo + + s.totalTrainers + s.totalMusicians + s.totalJudges + ); +} + function formatSats(sats: number): string { if (sats >= 1_000_000) return `${(sats / 1_000_000).toFixed(1)}M`; if (sats >= 1_000) return `${Math.round(sats / 1_000)}k`; @@ -162,7 +177,7 @@ export default function CommunityStatsPage() { return [ { name: 'Spots', value: stats.totalSpots, color: '#ff0040' }, { name: 'Events', value: stats.totalEvents, color: '#ccff00' }, - { name: 'Lab', value: stats.totalCoaches + stats.totalFilmers + stats.totalEditors + stats.totalPhotographers + stats.totalBuilders, color: '#ff6600' }, + { name: 'Lab', value: labCraftTotal(stats), color: '#ff6600' }, { name: 'Shops', value: stats.totalShops, color: '#4ade80' }, { name: 'Tours', value: stats.totalTours, color: '#c084fc' }, { name: 'Crews', value: stats.totalCrews, color: '#2dd4bf' }, @@ -176,17 +191,8 @@ export default function CommunityStatsPage() { // Current month delta — how many items were added this month const thisMonth = useMemo(() => stats?.monthly[stats.monthly.length - 1], [stats]); - // Total across all Lab creator/service roles - const labTotal = useMemo(() => { - if (!stats) return 0; - return ( - stats.totalCoaches + - stats.totalFilmers + - stats.totalEditors + - stats.totalPhotographers + - stats.totalBuilders - ); - }, [stats]); + // Total across ALL Lab creator/service crafts (matches what /lab lists) + const labTotal = useMemo(() => (stats ? labCraftTotal(stats) : 0), [stats]); return ( @@ -251,6 +257,12 @@ export default function CommunityStatsPage() { + + + + + +