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 <assistant@shakespeare.diy>
This commit is contained in:
parent
677ccb8a82
commit
94a88b2ba0
3 changed files with 61 additions and 18 deletions
|
|
@ -1,7 +1,7 @@
|
||||||
{
|
{
|
||||||
"name": "skate-spots",
|
"name": "skate-spots",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "1.2.0",
|
"version": "1.2.1",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"homepage": "https://skate-spots.com",
|
"homepage": "https://skate-spots.com",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ import {
|
||||||
SKATE_TATTOO_KIND,
|
SKATE_TATTOO_KIND,
|
||||||
SKATE_TRAINER_KIND,
|
SKATE_TRAINER_KIND,
|
||||||
SKATE_MUSICIAN_KIND,
|
SKATE_MUSICIAN_KIND,
|
||||||
|
SKATE_JUDGE_KIND,
|
||||||
SKATE_TOUR_KIND,
|
SKATE_TOUR_KIND,
|
||||||
SKATE_CREW_KIND,
|
SKATE_CREW_KIND,
|
||||||
} from '@/lib/types';
|
} from '@/lib/types';
|
||||||
|
|
@ -56,6 +57,13 @@ export interface GrowthStats {
|
||||||
totalEditors: number;
|
totalEditors: number;
|
||||||
totalPhotographers: number;
|
totalPhotographers: number;
|
||||||
totalBuilders: number;
|
totalBuilders: number;
|
||||||
|
// Additional Lab crafts
|
||||||
|
totalArtists: number;
|
||||||
|
totalGraffiti: number;
|
||||||
|
totalTattoo: number;
|
||||||
|
totalTrainers: number;
|
||||||
|
totalMusicians: number;
|
||||||
|
totalJudges: number;
|
||||||
totalTours: number;
|
totalTours: number;
|
||||||
totalCrews: number;
|
totalCrews: number;
|
||||||
/** All unique pubkeys that have published any content */
|
/** All unique pubkeys that have published any content */
|
||||||
|
|
@ -135,7 +143,12 @@ export function useGrowthStats() {
|
||||||
builderEvents,
|
builderEvents,
|
||||||
tourEvents,
|
tourEvents,
|
||||||
crewEvents,
|
crewEvents,
|
||||||
otherLabEvents,
|
artistEvents,
|
||||||
|
graffitiEvents,
|
||||||
|
tattooEvents,
|
||||||
|
trainerEvents,
|
||||||
|
musicianEvents,
|
||||||
|
judgeEvents,
|
||||||
] = await Promise.all([
|
] = await Promise.all([
|
||||||
querySkateContent([{ kinds: [SKATE_SPOT_KIND], '#t': ['skatespot'], limit: 2000 }], { signal }).catch(() => []),
|
querySkateContent([{ kinds: [SKATE_SPOT_KIND], '#t': ['skatespot'], limit: 2000 }], { signal }).catch(() => []),
|
||||||
querySkateContent([{ kinds: [SKATE_SHOP_KIND], 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_BUILDER_KIND], limit: 500 }], { signal }).catch(() => []),
|
||||||
querySkateContent([{ kinds: [SKATE_TOUR_KIND], limit: 500 }], { signal }).catch(() => []),
|
querySkateContent([{ kinds: [SKATE_TOUR_KIND], limit: 500 }], { signal }).catch(() => []),
|
||||||
querySkateContent([{ kinds: [SKATE_CREW_KIND], limit: 500 }], { signal }).catch(() => []),
|
querySkateContent([{ kinds: [SKATE_CREW_KIND], limit: 500 }], { signal }).catch(() => []),
|
||||||
// New Lab crafts (artist, graffiti, tattoo, trainer, musician) — folded
|
// All other Lab crafts, each counted individually so the stats page can
|
||||||
// into community totals/mappers without their own chart series.
|
// show a card per craft (matching what /lab lists).
|
||||||
querySkateContent([{ kinds: [SKATE_ARTIST_KIND, SKATE_GRAFFITI_KIND, SKATE_TATTOO_KIND, SKATE_TRAINER_KIND, SKATE_MUSICIAN_KIND], limit: 1000 }], { signal }).catch(() => []),
|
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)
|
// Deduplicate addressable events (keep newest per pubkey+kind+d)
|
||||||
|
|
@ -174,7 +192,14 @@ export function useGrowthStats() {
|
||||||
const builders = dedup(builderEvents);
|
const builders = dedup(builderEvents);
|
||||||
const tours = dedup(tourEvents);
|
const tours = dedup(tourEvents);
|
||||||
const crews = dedup(crewEvents);
|
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 ───────────────────────────────────────────────
|
// ── Monthly bucketing ───────────────────────────────────────────────
|
||||||
|
|
||||||
|
|
@ -289,6 +314,12 @@ export function useGrowthStats() {
|
||||||
totalEditors: editors.length,
|
totalEditors: editors.length,
|
||||||
totalPhotographers: photographers.length,
|
totalPhotographers: photographers.length,
|
||||||
totalBuilders: builders.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,
|
totalTours: tours.length,
|
||||||
totalCrews: crews.length,
|
totalCrews: crews.length,
|
||||||
totalMappers: allPubkeys.size,
|
totalMappers: allPubkeys.size,
|
||||||
|
|
|
||||||
|
|
@ -25,10 +25,25 @@ import {
|
||||||
Camera, Hammer,
|
Camera, Hammer,
|
||||||
TrendingUp, Radio, Zap, ExternalLink,
|
TrendingUp, Radio, Zap, ExternalLink,
|
||||||
Trophy, FlaskConical,
|
Trophy, FlaskConical,
|
||||||
|
Palette, SprayCan, PenTool, Dumbbell, Music, Gavel,
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
|
|
||||||
// ─── Helpers ─────────────────────────────────────────────────────────────────
|
// ─── 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 {
|
function formatSats(sats: number): string {
|
||||||
if (sats >= 1_000_000) return `${(sats / 1_000_000).toFixed(1)}M`;
|
if (sats >= 1_000_000) return `${(sats / 1_000_000).toFixed(1)}M`;
|
||||||
if (sats >= 1_000) return `${Math.round(sats / 1_000)}k`;
|
if (sats >= 1_000) return `${Math.round(sats / 1_000)}k`;
|
||||||
|
|
@ -162,7 +177,7 @@ export default function CommunityStatsPage() {
|
||||||
return [
|
return [
|
||||||
{ name: 'Spots', value: stats.totalSpots, color: '#ff0040' },
|
{ name: 'Spots', value: stats.totalSpots, color: '#ff0040' },
|
||||||
{ name: 'Events', value: stats.totalEvents, color: '#ccff00' },
|
{ 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: 'Shops', value: stats.totalShops, color: '#4ade80' },
|
||||||
{ name: 'Tours', value: stats.totalTours, color: '#c084fc' },
|
{ name: 'Tours', value: stats.totalTours, color: '#c084fc' },
|
||||||
{ name: 'Crews', value: stats.totalCrews, color: '#2dd4bf' },
|
{ 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
|
// Current month delta — how many items were added this month
|
||||||
const thisMonth = useMemo(() => stats?.monthly[stats.monthly.length - 1], [stats]);
|
const thisMonth = useMemo(() => stats?.monthly[stats.monthly.length - 1], [stats]);
|
||||||
|
|
||||||
// Total across all Lab creator/service roles
|
// Total across ALL Lab creator/service crafts (matches what /lab lists)
|
||||||
const labTotal = useMemo(() => {
|
const labTotal = useMemo(() => (stats ? labCraftTotal(stats) : 0), [stats]);
|
||||||
if (!stats) return 0;
|
|
||||||
return (
|
|
||||||
stats.totalCoaches +
|
|
||||||
stats.totalFilmers +
|
|
||||||
stats.totalEditors +
|
|
||||||
stats.totalPhotographers +
|
|
||||||
stats.totalBuilders
|
|
||||||
);
|
|
||||||
}, [stats]);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Layout>
|
<Layout>
|
||||||
|
|
@ -251,6 +257,12 @@ export default function CommunityStatsPage() {
|
||||||
<StatCard icon={Scissors} value={stats?.totalEditors ?? 0} label="Editors" color="#c084fc" loading={isLoading} thisMonthDelta={thisMonth?.editors} />
|
<StatCard icon={Scissors} value={stats?.totalEditors ?? 0} label="Editors" color="#c084fc" loading={isLoading} thisMonthDelta={thisMonth?.editors} />
|
||||||
<StatCard icon={Camera} value={stats?.totalPhotographers ?? 0} label="Photographers" color="#38bdf8" loading={isLoading} thisMonthDelta={thisMonth?.photographers} />
|
<StatCard icon={Camera} value={stats?.totalPhotographers ?? 0} label="Photographers" color="#38bdf8" loading={isLoading} thisMonthDelta={thisMonth?.photographers} />
|
||||||
<StatCard icon={Hammer} value={stats?.totalBuilders ?? 0} label="Builders" color="#fb923c" loading={isLoading} thisMonthDelta={thisMonth?.builders} />
|
<StatCard icon={Hammer} value={stats?.totalBuilders ?? 0} label="Builders" color="#fb923c" loading={isLoading} thisMonthDelta={thisMonth?.builders} />
|
||||||
|
<StatCard icon={Palette} value={stats?.totalArtists ?? 0} label="Artists" color="#f472b6" loading={isLoading} />
|
||||||
|
<StatCard icon={SprayCan} value={stats?.totalGraffiti ?? 0} label="Graffiti" color="#a3e635" loading={isLoading} />
|
||||||
|
<StatCard icon={PenTool} value={stats?.totalTattoo ?? 0} label="Tattoo" color="#f87171" loading={isLoading} />
|
||||||
|
<StatCard icon={Dumbbell} value={stats?.totalTrainers ?? 0} label="Trainers" color="#34d399" loading={isLoading} />
|
||||||
|
<StatCard icon={Music} value={stats?.totalMusicians ?? 0} label="Musicians" color="#818cf8" loading={isLoading} />
|
||||||
|
<StatCard icon={Gavel} value={stats?.totalJudges ?? 0} label="Judges" color="#fbbf24" loading={isLoading} />
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue