chore: audit fixes, designer stats and form FAB handling

This commit is contained in:
SkateSpots 2026-10-29 23:14:12 +07:00
parent dddfe51f86
commit 262eb32e7c
33 changed files with 53 additions and 24 deletions

13
package-lock.json generated
View file

@ -2946,12 +2946,6 @@
"integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==",
"license": "BSD-3-Clause"
},
"node_modules/@protobufjs/inquire": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.2.tgz",
"integrity": "sha512-pa0vFRuws4wkvaXKK1uXZMAwAX4/t8ANaJo45iw/oQHNQ9q5xUzwgFmVJGXiga2BeN+zpX7Vf9vmsiIa2J+MUw==",
"license": "BSD-3-Clause"
},
"node_modules/@protobufjs/path": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz",
@ -10926,9 +10920,9 @@
"license": "MIT"
},
"node_modules/protobufjs": {
"version": "7.6.3",
"resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.6.3.tgz",
"integrity": "sha512-+k0vdJKNdW+Vu+dYe8tZA/VvQb6XKNWexC6URwBFXxNnjLJz9nQJCemGyNgRAWD+B7+nGNc9qMPGwcD7s4nzUw==",
"version": "7.6.5",
"resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.6.5.tgz",
"integrity": "sha512-/FPD0nUc9jH6rfFjji9IBqOz4pcSE3CsT1m7Ep6Mdb0LxSUMj8hgl6GomOvZzpNpAqqGaXA0P3VSrZLFzIhQrw==",
"hasInstallScript": true,
"license": "BSD-3-Clause",
"dependencies": {
@ -10938,7 +10932,6 @@
"@protobufjs/eventemitter": "^1.1.1",
"@protobufjs/fetch": "^1.1.1",
"@protobufjs/float": "^1.0.2",
"@protobufjs/inquire": "^1.1.2",
"@protobufjs/path": "^1.1.2",
"@protobufjs/pool": "^1.1.0",
"@protobufjs/utf8": "^1.1.1",

View file

@ -184,7 +184,7 @@
},
"overrides": {
"@tanstack/query-core": "$@tanstack/react-query",
"protobufjs": "7.6.3",
"protobufjs": "7.6.5",
"sharp": "0.35.5"
}
}

View file

@ -522,6 +522,8 @@ export function Layout({ children, noPadding }: LayoutProps) {
// The map is full-bleed (it manages its own height), so it gets no bottom
// padding even though it shares this one persistent layout with every route.
const isFullBleed = noPadding || location.pathname === '/' || location.pathname === '/map';
// The floating assistant must not cover form controls on long-form pages.
const hideSasquatch = /^\/(become|create|edit|supporters|onboarding)(\/|$)/.test(location.pathname);
const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
const [mobileLanguageView, setMobileLanguageView] = useState(false);
// One auth dialog for the header + burger menu. Owning it here (not inside
@ -1262,12 +1264,14 @@ export function Layout({ children, noPadding }: LayoutProps) {
</Suspense>
{/* Sasquatch Guide (all screens) — lazy; not needed at first paint */}
<Suspense fallback={null}>
<LazySasquatchGuide
message={currentMessage}
onDismiss={dismissCurrentMessage}
/>
</Suspense>
{!hideSasquatch && (
<Suspense fallback={null}>
<LazySasquatchGuide
message={currentMessage}
onDismiss={dismissCurrentMessage}
/>
</Suspense>
)}
{/* Set Status Dialog — lazy (on-demand modal) */}
<Suspense fallback={null}>

View file

@ -56,7 +56,7 @@ export function useFollowers(pubkey?: string) {
// Query all kind 3 events that have a p tag with our pubkey
const events = await nostr.query(
[{ kinds: [3], '#p': [targetPubkey], limit: 100 }],
[{ kinds: [3], '#p': [targetPubkey], limit: 500 }],
{ signal }
);

View file

@ -17,7 +17,7 @@ import {
SKATE_MUSICIAN_KIND,
SKATE_JUDGE_KIND,
SKATE_ORGANIZER_KIND,
SKATE_BARBER_KIND, SKATE_TRANSPORT_KIND,
SKATE_BARBER_KIND, SKATE_TRANSPORT_KIND, SKATE_DESIGNER_KIND,
SKATE_TOUR_KIND,
SKATE_CREW_KIND,
MARKETPLACE_ITEM_KIND,
@ -80,6 +80,7 @@ export interface GrowthStats {
totalOrganizers: number;
totalBarbers: number;
totalTransport: number;
totalDesigners: number;
totalTours: number;
totalCrews: number;
/** All unique pubkeys that have published any content */
@ -232,6 +233,7 @@ export function useGrowthStats() { // Community stats read from the CANONICAL c
organizerEvents,
barberEvents,
transportEvents,
designerEvents,
listingEvents,
tradeEvents,
] = await Promise.all([
@ -256,6 +258,7 @@ export function useGrowthStats() { // Community stats read from the CANONICAL c
querySkateContent([{ kinds: [SKATE_ORGANIZER_KIND], limit: 500 }], { signal }).catch(() => []),
querySkateContent([{ kinds: [SKATE_BARBER_KIND], limit: 500 }], { signal }).catch(() => []),
querySkateContent([{ kinds: [SKATE_TRANSPORT_KIND], limit: 500 }], { signal }).catch(() => []),
querySkateContent([{ kinds: [SKATE_DESIGNER_KIND], limit: 500 }], { signal }).catch(() => []),
// Marketplace: live listings + completed-trade attestations.
querySkateContent([{ kinds: [MARKETPLACE_ITEM_KIND], '#t': ['skateboarding'], limit: 2000 }], { signal }).catch(() => []),
querySkateContent([{ kinds: [TRADE_ATTESTATION_KIND], limit: 1000 }], { signal }).catch(() => []),
@ -299,8 +302,9 @@ export function useGrowthStats() { // Community stats read from the CANONICAL c
const organizers = dedup(organizerEvents);
const barbers = dedup(barberEvents);
const transport = dedup(transportEvents);
const designers = dedup(designerEvents);
// Aggregate of the "other" crafts for active-mapper / total rollups.
const otherLab = [...artists, ...graffiti, ...tattoo, ...trainers, ...musicians, ...judges, ...organizers, ...barbers, ...transport];
const otherLab = [...artists, ...graffiti, ...tattoo, ...trainers, ...musicians, ...judges, ...organizers, ...barbers, ...transport, ...designers];
// ── Marketplace ──────────────────────────────────────────────────────
// Showcase-only stash items are public but NOT for sale, so exclude them.
@ -579,6 +583,7 @@ export function useGrowthStats() { // Community stats read from the CANONICAL c
totalOrganizers: organizers.length,
totalBarbers: barbers.length,
totalTransport: transport.length,
totalDesigners: designers.length,
totalTours: tours.length,
totalCrews: crews.length,
totalMappers: allPubkeys.size,

View file

@ -714,6 +714,7 @@ const batch6 = {
judges: "Jutges",
organizers: "Organitzadors",
barbers: "Barbers",
designers: "Dissenyadors",
showFewer: "Mostra'n menys",
showAllCrafts: "Mostra els 15 oficis",
community: "Comunitat",

View file

@ -688,6 +688,7 @@ export const csBatch4 = {
judges: "Rozhodčí",
organizers: "Organizátoři",
barbers: "Barbeři",
designers: "Designéři",
showFewer: "Zobrazit méně",
showAllCrafts: "Zobrazit všech 15 řemesel",
community: "Komunita",

View file

@ -688,6 +688,7 @@ const deBatch4 = {
judges: "Juroren",
organizers: "Organisatoren",
barbers: "Barbiere",
designers: "Designer:innen",
showFewer: "Weniger anzeigen",
showAllCrafts: "Alle 15 Handwerke anzeigen",
community: "Community",

View file

@ -688,6 +688,7 @@ const elBatch4 = {
judges: "Κριτές",
organizers: "Διοργανωτές",
barbers: "Κουρείς",
designers: "Σχεδιαστές",
showFewer: "Εμφάνιση λιγότερων",
showAllCrafts: "Εμφάνιση και των 15 ειδικοτήτων",
community: "Κοινότητα",

View file

@ -6521,6 +6521,7 @@ walletUi: {
organizers: 'Organizers',
barbers: 'Barbers',
transport: 'Transport',
designers: 'Designers',
showFewer: 'Show fewer',
showAllCrafts: 'Show all 15 crafts',
community: 'Community',

View file

@ -6521,6 +6521,7 @@ walletUi: {
organizers: 'Organizadores',
barbers: 'Barberos',
transport: 'Transporte',
designers: 'Diseñadores',
showFewer: 'Mostrar menos',
showAllCrafts: 'Mostrar los 15 oficios',
community: 'Comunidad',

View file

@ -714,6 +714,7 @@ const batch6 = {
judges: "Epaileak",
organizers: "Antolatzaileak",
barbers: "Bizarginak",
designers: "Diseinatzaileak",
showFewer: "Erakutsi gutxiago",
showAllCrafts: "Erakutsi 15 lanbide guztiak",
community: "Komunitatea",

View file

@ -688,6 +688,7 @@ const frBatch4 = {
judges: "Juges",
organizers: "Organisateurs",
barbers: "Coiffeurs",
designers: "Concepteurs",
showFewer: "Afficher moins",
showAllCrafts: "Afficher les 15 disciplines",
community: "Communauté",

View file

@ -714,6 +714,7 @@ const batch6 = {
"judges": "Xuíces",
"organizers": "Organizadores",
"barbers": "Barbeiros",
"designers": "Deseñadores",
"showFewer": "Amosar menos",
"showAllCrafts": "Amosar os 15 oficios",
"community": "Comunidade",

View file

@ -714,6 +714,7 @@ const batch6 = {
"judges": "जज",
"organizers": "आयोजक",
"barbers": "नाई",
"designers": "डिज़ाइनर",
"showFewer": "कम दिखाएँ",
"showAllCrafts": "सभी 15 क्राफ़्ट दिखाएँ",
"community": "समुदाय",

View file

@ -694,6 +694,7 @@ const idBatch4 = {
judges: 'Juri',
organizers: 'Penyelenggara',
barbers: 'Tukang Cukur',
designers: 'Desainer',
showFewer: 'Tampilkan lebih sedikit',
showAllCrafts: 'Tampilkan semua 15 keahlian',
community: 'Komunitas',

View file

@ -688,6 +688,7 @@ const itBatch4 = {
judges: "Giudici",
organizers: "Organizzatori",
barbers: "Barbieri",
designers: "Progettisti",
showFewer: "Mostra meno",
showAllCrafts: "Mostra tutti i 15 mestieri",
community: "Community",

View file

@ -697,6 +697,7 @@ const jaBatch7 = {
judges: 'ジャッジ',
organizers: 'オーガナイザー',
barbers: 'バーバー',
designers: 'デザイナー',
showFewer: '表示を減らす',
showAllCrafts: '15職種すべて表示',
community: 'コミュニティ',

View file

@ -696,6 +696,7 @@ const koBatch4 = {
judges: '심판',
organizers: '주최자',
barbers: '바버',
designers: '디자이너',
showFewer: '접기',
showAllCrafts: '15개 분야 모두 보기',
community: '커뮤니티',

View file

@ -696,6 +696,7 @@ const nlBatch4 = {
judges: 'Juryleden',
organizers: 'Organisatoren',
barbers: 'Barbers',
designers: 'Ontwerpers',
showFewer: 'Minder tonen',
showAllCrafts: 'Alle 15 ambachten tonen',
community: 'Community',

View file

@ -688,6 +688,7 @@ const plBatch4 = {
judges: "Sędziowie",
organizers: "Organizatorzy",
barbers: "Barberzy",
designers: "Projektanci",
showFewer: "Pokaż mniej",
showAllCrafts: "Pokaż wszystkie 15 rzemiosł",
community: "Społeczność",

View file

@ -775,6 +775,7 @@ const ptBatch4 = {
judges: 'Jurados',
organizers: 'Organizadores',
barbers: 'Barbeiros',
designers: 'Projetistas',
showFewer: 'Mostrar menos',
showAllCrafts: 'Mostrar todos os 15 ofícios',
community: 'Comunidade',

View file

@ -688,6 +688,7 @@ const roBatch4 = {
judges: 'Juri',
organizers: 'Organizatori',
barbers: 'Frizeri',
designers: 'Designeri',
showFewer: 'Arată mai puține',
showAllCrafts: 'Arată toate cele 15 meserii',
community: 'Comunitate',

View file

@ -688,6 +688,7 @@ const ruBatch4 = {
judges: 'Судьи',
organizers: 'Организаторы',
barbers: 'Барберы',
designers: 'Дизайнеры',
showFewer: 'Показать меньше',
showAllCrafts: 'Показать все 15 ремёсел',
community: 'Сообщество',

View file

@ -688,6 +688,7 @@ const svBatch4 = {
judges: "Domare",
organizers: "Arrangörer",
barbers: "Barberare",
designers: "Formgivare",
showFewer: "Visa färre",
showAllCrafts: "Visa alla 15 hantverk",
community: "Community",

View file

@ -768,6 +768,7 @@ const thBatch9 = {
judges: 'กรรมการ',
organizers: 'ผู้จัด',
barbers: 'ช่างตัดผม',
designers: 'นักออกแบบ',
showFewer: 'แสดงน้อยลง',
showAllCrafts: 'แสดงทั้ง 15 อาชีพ',
community: 'ชุมชน',

View file

@ -688,6 +688,7 @@ const trBatch4 = {
judges: "Jüri üyeleri",
organizers: "Organizatörler",
barbers: "Berberler",
designers: "Tasarımcılar",
showFewer: "Daha az göster",
showAllCrafts: "15 zanaatın tümünü göster",
community: "Topluluk",

View file

@ -688,6 +688,7 @@ const ukBatch4 = {
judges: "Судді",
organizers: "Організатори",
barbers: "Барбери",
designers: "Дизайнери",
showFewer: "Показати менше",
showAllCrafts: "Показати всі 15 напрямів",
community: "Спільнота",

View file

@ -696,6 +696,7 @@ const viBatch4 = {
judges: 'Giám khảo',
organizers: 'Nhà tổ chức',
barbers: 'Thợ cắt tóc',
designers: 'Nhà thiết kế',
showFewer: 'Hiện ít hơn',
showAllCrafts: 'Hiện cả 15 nghề',
community: 'Cộng đồng',

View file

@ -693,6 +693,7 @@ const zhCNBatch4 = {
judges: "裁判",
organizers: "组织者",
barbers: "理发师",
designers: "设计师",
showFewer: "收起",
showAllCrafts: "显示全部 15 种手艺",
community: "社区",

View file

@ -693,6 +693,7 @@ const zhTWBatch4 = {
judges: "裁判",
organizers: "組織者",
barbers: "理髮師",
designers: "設計師",
showFewer: "收起",
showAllCrafts: "顯示全部 15 種手藝",
community: "社群",

View file

@ -16,7 +16,7 @@ import { formatAge } from '@/lib/routeCache';
const ANGOR_PROJECT_URL = 'https://angor.io/project/angor1qtunna00uqyx52rr0nvqa059z2gknhrmn7urd6z';
import { SKATE_RELAY } from '@/lib/types';
import { AreaChart, Area, BarChart, Bar, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, PieChart, Pie, Cell, Legend } from 'recharts';
import { MapPin, Store, Calendar, GraduationCap, Video, Scissors, Route, Users, Users2, Camera, Hammer, TrendingUp, Radio, Zap, ExternalLink, Trophy, FlaskConical, Palette, SprayCan, PenTool, Dumbbell, Music, Gavel, Tent, Sparkles, Truck, ShoppingBag, Tag, Handshake, Globe, RefreshCw } from 'lucide-react';
import { MapPin, Store, Calendar, GraduationCap, Video, Scissors, Route, Users, Users2, Camera, Hammer, TrendingUp, Radio, Zap, ExternalLink, Trophy, FlaskConical, Palette, SprayCan, PenTool, Dumbbell, Music, Gavel, Tent, Sparkles, Truck, Shirt, ShoppingBag, Tag, Handshake, Globe, RefreshCw } from 'lucide-react';
// ─── Helpers ─────────────────────────────────────────────────────────────────
@ -25,13 +25,13 @@ 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; totalOrganizers: number; totalBarbers: number; totalTransport: number;
totalMusicians: number; totalJudges: number; totalOrganizers: number; totalBarbers: number; totalTransport: number; totalDesigners: 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 + s.totalOrganizers +
s.totalBarbers + (s.totalTransport ?? 0)
s.totalBarbers + (s.totalTransport ?? 0) + (s.totalDesigners ?? 0)
);
}
@ -323,6 +323,7 @@ export default function CommunityStatsPage() {
<StatCard icon={Tent} value={stats?.totalOrganizers ?? 0} label={t('walletUi.stats.organizers')} color="#2dd4bf" loading={isLoading} to="/lab?craft=organizing" />
<StatCard icon={Sparkles} value={stats?.totalBarbers ?? 0} label={t('walletUi.stats.barbers')} color="#22d3ee" loading={isLoading} to="/lab?craft=barbering" />
<StatCard icon={Truck} value={stats?.totalTransport ?? 0} label={t('walletUi.stats.transport')} color="#f59e0b" loading={isLoading} to="/lab?craft=transport" />
<StatCard icon={Shirt} value={stats?.totalDesigners ?? 0} label={t('walletUi.stats.designers')} color="#a78bfa" loading={isLoading} to="/lab?craft=clothing" />
</div>
</div>
<button

View file

@ -628,7 +628,7 @@ export default function SupportersPage() {
? num(stats.totalCoaches) + num(stats.totalFilmers) + num(stats.totalEditors) + num(stats.totalPhotographers)
+ num(stats.totalBuilders) + num(stats.totalArtists) + num(stats.totalGraffiti) + num(stats.totalTattoo)
+ num(stats.totalTrainers) + num(stats.totalMusicians) + num(stats.totalJudges) + num(stats.totalOrganizers)
+ num(stats.totalBarbers) + num(stats.totalTransport)
+ num(stats.totalBarbers) + num(stats.totalTransport) + num(stats.totalDesigners)
: 0;
const impact = stats