Fix newsletter admin gating: use isAdminPubkey from lib/admins instead of undefined NEWSLETTER_ADMIN_PUBKEYS

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
This commit is contained in:
shakespeare.diy 2026-08-20 16:16:09 +07:00
parent c683b7e8bf
commit 13934c0e74
4 changed files with 49 additions and 19 deletions

View file

@ -4,17 +4,7 @@ import { X, Radio, Pencil } from 'lucide-react';
import { useCurrentUser } from '@/hooks/useCurrentUser'; import { useCurrentUser } from '@/hooks/useCurrentUser';
import { useLocalStorage } from '@/hooks/useLocalStorage'; import { useLocalStorage } from '@/hooks/useLocalStorage';
import { NEWSLETTER_ISSUES } from '@/pages/NewsletterPage'; import { NEWSLETTER_ISSUES } from '@/pages/NewsletterPage';
import { isAdminPubkey } from '@/lib/admins';
// ─── 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
];
/** /**
* How many days the "Latest Drop" pill stays visible after a new issue. * 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 // Today's date key — so it only shows once per Monday, not every page load
const todayKey = new Date().toISOString().slice(0, 10); 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; const alreadyDismissedToday = dismissed === todayKey;
if ( if (

View file

@ -1,9 +1,9 @@
import { useQuery } from '@tanstack/react-query'; import { useQuery } from '@tanstack/react-query';
import { import {
SKATE_COACH_KIND, SKATE_FILMER_KIND, SKATE_EDITOR_KIND, SKATE_PHOTOGRAPHER_KIND, SKATE_BUILDER_KIND, 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, parseSkateCoach, parseSkateFilmer, parseSkateEditor, parseSkatePhotographer, parseSkateBuilder,
parseSkateArtist, parseSkateGraffiti, parseSkateTattoo, parseSkateTrainer, parseSkateMusician, parseSkateArtist, parseSkateGraffiti, parseSkateTattoo, parseSkateTrainer, parseSkateMusician, parseSkateJudge,
isFeaturedActive, isFeaturedActive,
type SkateCoach, type SkateFilmer, type SkateEditor, type SkatePhotographer, type SkateBuilder, type SkateCoach, type SkateFilmer, type SkateEditor, type SkatePhotographer, type SkateBuilder,
type SkateArtist, type SkateGraffiti, type SkateTattoo, type SkateTrainer, type SkateMusician, type SkateArtist, type SkateGraffiti, type SkateTattoo, type SkateTrainer, type SkateMusician,
@ -14,7 +14,7 @@ import { useAdminModeration, filterAdminHiddenMixed } from '@/hooks/useAdminMode
export type LabProfileType = export type LabProfileType =
| 'coach' | 'filmer' | 'editor' | 'photographer' | 'builder' | 'coach' | 'filmer' | 'editor' | 'photographer' | 'builder'
| 'artist' | 'graffiti' | 'tattoo' | 'trainer' | 'musician'; | 'artist' | 'graffiti' | 'tattoo' | 'trainer' | 'musician' | 'judge';
export type LabProfile = ( export type LabProfile = (
| SkateCoach | SkateFilmer | SkateEditor | SkatePhotographer | SkateBuilder | SkateCoach | SkateFilmer | SkateEditor | SkatePhotographer | SkateBuilder
@ -25,12 +25,13 @@ export type LabProfile = (
const LAB_KINDS = [ const LAB_KINDS = [
SKATE_COACH_KIND, SKATE_FILMER_KIND, SKATE_EDITOR_KIND, SKATE_PHOTOGRAPHER_KIND, SKATE_BUILDER_KIND, 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 = [ const LAB_SELF_TAGS = [
'skatecoach', 'skatefilmer', 'skateeditor', 'skatephotographer', 'skatebuilder', '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. */ /** Parse any Lab event into a LabProfile, or null if invalid / unknown kind. */

View file

@ -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_TATTOO_KIND = 35009; // Tattoo artist profiles
export const SKATE_TRAINER_KIND = 35010; // Physical trainer / physiotherapist 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_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 MARKETPLACE_ITEM_KIND = 30402; // NIP-99 Classified Listings
export const SPOT_COLLECTION_KIND = 30003; // NIP-51 Bookmark sets for spot collections 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 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 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; if (event.kind !== SKATE_COACH_KIND) return null;
const d = event.tags.find(([name]) => name === 'd')?.[1]; const d = event.tags.find(([name]) => name === 'd')?.[1];
@ -2798,3 +2810,30 @@ export function parseMarketplaceItem(event: NostrEvent): MarketplaceItem | null
event, 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;
}

View file

@ -729,7 +729,7 @@ function PublishToNostrButton({ issue }: { issue: NewsletterIssue }) {
const [published, setPublished] = useState(false); const [published, setPublished] = useState(false);
const [showPreview, setShowPreview] = 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 (!user || !isAdmin) return null;
if (published) { if (published) {