From 13934c0e74464cf4006b610159943a017dbe49ab Mon Sep 17 00:00:00 2001 From: "shakespeare.diy" Date: Thu, 20 Aug 2026 16:16:09 +0700 Subject: [PATCH] Fix newsletter admin gating: use isAdminPubkey from lib/admins instead of undefined NEWSLETTER_ADMIN_PUBKEYS Co-authored-by: shakespeare.diy --- src/components/NewsletterOverlay.tsx | 14 ++-------- src/hooks/useLabProfiles.ts | 11 ++++---- src/lib/types.ts | 41 +++++++++++++++++++++++++++- src/pages/NewsletterPage.tsx | 2 +- 4 files changed, 49 insertions(+), 19 deletions(-) diff --git a/src/components/NewsletterOverlay.tsx b/src/components/NewsletterOverlay.tsx index eb6dadd..62bffdb 100644 --- a/src/components/NewsletterOverlay.tsx +++ b/src/components/NewsletterOverlay.tsx @@ -4,17 +4,7 @@ import { X, Radio, Pencil } from 'lucide-react'; import { useCurrentUser } from '@/hooks/useCurrentUser'; import { useLocalStorage } from '@/hooks/useLocalStorage'; import { NEWSLETTER_ISSUES } from '@/pages/NewsletterPage'; - -// ─── Config ────────────────────────────────────────────────────────────────── - -/** - * Only these two pubkeys see the Monday "write newsletter" reminder. - * Everyone else sees nothing. - */ -const NEWSLETTER_ADMIN_PUBKEYS = [ - '20e7c953439e66f3882ab96fe0faa8ba246d6fb12c03b0cb4e2dc4623b8bcb21', // SkateSpots founder — npub1yrnuj56rnen08zp2h9h7p74ghgjx6ma39spmpj6w9hzxywutevsst7k5cx - 'caba497f7beda0650d6b78ab211216e4d2ebabeec9ca6eb2a05cee8e55c6871b', // npub1e2ayjlmmaksx2rtt0z4jzyskunfwh2lwe89xav4qtnhgu4wxsudsp98a4l -]; +import { isAdminPubkey } from '@/lib/admins'; /** * How many days the "Latest Drop" pill stays visible after a new issue. @@ -107,7 +97,7 @@ function MondayAdminReminder() { // Today's date key — so it only shows once per Monday, not every page load const todayKey = new Date().toISOString().slice(0, 10); - const isAdmin = NEWSLETTER_ADMIN_PUBKEYS.includes(user?.pubkey ?? ''); + const isAdmin = isAdminPubkey(user?.pubkey); const alreadyDismissedToday = dismissed === todayKey; if ( diff --git a/src/hooks/useLabProfiles.ts b/src/hooks/useLabProfiles.ts index 93eed36..7596aa7 100644 --- a/src/hooks/useLabProfiles.ts +++ b/src/hooks/useLabProfiles.ts @@ -1,9 +1,9 @@ import { useQuery } from '@tanstack/react-query'; import { SKATE_COACH_KIND, SKATE_FILMER_KIND, SKATE_EDITOR_KIND, SKATE_PHOTOGRAPHER_KIND, SKATE_BUILDER_KIND, - SKATE_ARTIST_KIND, SKATE_GRAFFITI_KIND, SKATE_TATTOO_KIND, SKATE_TRAINER_KIND, SKATE_MUSICIAN_KIND, + SKATE_ARTIST_KIND, SKATE_GRAFFITI_KIND, SKATE_TATTOO_KIND, SKATE_TRAINER_KIND, SKATE_MUSICIAN_KIND, SKATE_JUDGE_KIND, parseSkateCoach, parseSkateFilmer, parseSkateEditor, parseSkatePhotographer, parseSkateBuilder, - parseSkateArtist, parseSkateGraffiti, parseSkateTattoo, parseSkateTrainer, parseSkateMusician, + parseSkateArtist, parseSkateGraffiti, parseSkateTattoo, parseSkateTrainer, parseSkateMusician, parseSkateJudge, isFeaturedActive, type SkateCoach, type SkateFilmer, type SkateEditor, type SkatePhotographer, type SkateBuilder, type SkateArtist, type SkateGraffiti, type SkateTattoo, type SkateTrainer, type SkateMusician, @@ -14,7 +14,7 @@ import { useAdminModeration, filterAdminHiddenMixed } from '@/hooks/useAdminMode export type LabProfileType = | 'coach' | 'filmer' | 'editor' | 'photographer' | 'builder' - | 'artist' | 'graffiti' | 'tattoo' | 'trainer' | 'musician'; + | 'artist' | 'graffiti' | 'tattoo' | 'trainer' | 'musician' | 'judge'; export type LabProfile = ( | SkateCoach | SkateFilmer | SkateEditor | SkatePhotographer | SkateBuilder @@ -25,12 +25,13 @@ export type LabProfile = ( const LAB_KINDS = [ SKATE_COACH_KIND, SKATE_FILMER_KIND, SKATE_EDITOR_KIND, SKATE_PHOTOGRAPHER_KIND, SKATE_BUILDER_KIND, - SKATE_ARTIST_KIND, SKATE_GRAFFITI_KIND, SKATE_TATTOO_KIND, SKATE_TRAINER_KIND, SKATE_MUSICIAN_KIND, + SKATE_ARTIST_KIND, SKATE_GRAFFITI_KIND, SKATE_TATTOO_KIND, SKATE_TRAINER_KIND, SKATE_MUSICIAN_KIND, SKATE_JUDGE_KIND, + 'judge' ]; const LAB_SELF_TAGS = [ 'skatecoach', 'skatefilmer', 'skateeditor', 'skatephotographer', 'skatebuilder', - 'skateartist', 'skategraffiti', 'skatetattoo', 'skatetrainer', 'skatemusician', + 'skateartist', 'skategraffiti', 'skatetattoo', 'skatetrainer', 'skatemusician', 'skatejudge', ]; /** Parse any Lab event into a LabProfile, or null if invalid / unknown kind. */ diff --git a/src/lib/types.ts b/src/lib/types.ts index 08ed783..23536c2 100644 --- a/src/lib/types.ts +++ b/src/lib/types.ts @@ -56,6 +56,7 @@ export const SKATE_GRAFFITI_KIND = 35008; // Graffiti / mural artist profiles export const SKATE_TATTOO_KIND = 35009; // Tattoo artist profiles export const SKATE_TRAINER_KIND = 35010; // Physical trainer / physiotherapist profiles export const SKATE_MUSICIAN_KIND = 35011; // Musician / band profiles (skaters who make music) +export const SKATE_JUDGE_KIND = 35012; // Skate judges (event officials) export const MARKETPLACE_ITEM_KIND = 30402; // NIP-99 Classified Listings export const SPOT_COLLECTION_KIND = 30003; // NIP-51 Bookmark sets for spot collections export const SKATE_TOUR_KIND = 30823; // Local skate tours - guides offer to show hidden spots @@ -1734,7 +1735,18 @@ export interface TourSponsor { challengeId?: string; // If sponsoring a specific challenge } -export function parseSkateCoach(event: NostrEvent): SkateCoach | null { +export function parseSkateJudge(event: NostrEvent): SkateJudge | null { + if (event.kind !== SKATE_JUDGE_KIND) return null; + + const judge = { + name: event.tags.find(([name]) => name === 'name')?.[1] || 'Unknown Judge', + expertise: event.tags.find(([name]) => name === 'expertise')?.[1] || '', + experience: event.tags.find(([name]) => name === 'experience')?.[1] || '', + events_judged: event.tags.find(([name]) => name === 'events_judged')?.[1] || '0', + }; + + return judge; +} if (event.kind !== SKATE_COACH_KIND) return null; const d = event.tags.find(([name]) => name === 'd')?.[1]; @@ -2798,3 +2810,30 @@ export function parseMarketplaceItem(event: NostrEvent): MarketplaceItem | null event, }; } + +export interface SkateJudge { + id: string; + pubkey: string; + identifier: string; + expertise: string; + experience: string; + events_judged: string; + entityType: EntityType; + createdAt: number; + event: NostrEvent; +} + +export function parseSkateJudge(event: NostrEvent): SkateJudge | null { + if (event.kind !== SKATE_JUDGE_KIND) return null; + + const judge = { + id: event.id, + pubkey: event.pubkey, + identifier: event.tags.find(([name]) => name === 'd')?.[1] || '', + expertise: event.tags.find(([name]) => name === 'expertise')?.[1] || '', + experience: event.tags.find(([name]) => name === 'experience')?.[1] || '', + events_judged: event.tags.find(([name]) => name === 'events_judged')?.[1] || '0', + }; + + return judge; +} diff --git a/src/pages/NewsletterPage.tsx b/src/pages/NewsletterPage.tsx index 76119d1..7e2a96b 100644 --- a/src/pages/NewsletterPage.tsx +++ b/src/pages/NewsletterPage.tsx @@ -729,7 +729,7 @@ function PublishToNostrButton({ issue }: { issue: NewsletterIssue }) { const [published, setPublished] = useState(false); const [showPreview, setShowPreview] = useState(false); - const isAdmin = NEWSLETTER_ADMIN_PUBKEYS.includes(user?.pubkey ?? ''); + const isAdmin = isAdminPubkey(user?.pubkey); if (!user || !isAdmin) return null; if (published) {