Spot detail: make mapper clickable + add Message Mapper button

The "Tip the Mapper" card now links the avatar/name to the mapper's profile
(/{npub} via the NIP-19 router) and adds a "Message Mapper" button that opens
an encrypted DM (/messages/{npub}). Makes it easy to contact whoever added a
spot — e.g. to ask them to add photos to a photoless submission. The message
button is hidden when you're viewing your own spot.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
This commit is contained in:
shakespeare.diy 2026-08-15 10:03:58 +07:00
parent aba79900e4
commit 8b080fb7f1
3 changed files with 55 additions and 11 deletions

22
src/lib/admins.ts Normal file
View file

@ -0,0 +1,22 @@
// ─── SkateSpots admin identities ────────────────────────────────────────────
//
// Single source of truth for who counts as an "admin" across the app
// (newsletter publishing, relay admin dashboard, etc.).
//
// Pubkeys are stored in HEX because that's what `user.pubkey` and Nostr
// filters use. The bech32 npub is kept in a comment for human readability.
/** Hex pubkeys of SkateSpots admins. */
export const ADMIN_PUBKEYS: string[] = [
// SkateSpots founder
// npub1yrnuj56rnen08zp2h9h7p74ghgjx6ma39spmpj6w9hzxywutevsst7k5cx
'20e7c953439e66f3882ab96fe0faa8ba246d6fb12c03b0cb4e2dc4623b8bcb21',
// npub1e2ayjlmmaksx2rtt0z4jzyskunfwh2lwe89xav4qtnhgu4wxsudsp98a4l
'caba497f7beda0650d6b78ab211216e4d2ebabeec9ca6eb2a05cee8e55c6871b',
];
/** Returns true if the given hex pubkey belongs to a SkateSpots admin. */
export function isAdminPubkey(pubkey: string | undefined | null): boolean {
if (!pubkey) return false;
return ADMIN_PUBKEYS.includes(pubkey);
}

View file

@ -28,10 +28,7 @@ import { Avatar, AvatarImage, AvatarFallback } from '@/components/ui/avatar';
// ─── Admin pubkeys allowed to publish newsletter as kind 1 ───────────────────
const NEWSLETTER_ADMIN_PUBKEYS = [
'20e7c953439e66f3882ab96fe0faa8ba246d6fb12c03b0cb4e2dc4623b8bcb21',
'caba497f7beda0650d6b78ab211216e4d2ebabeec9ca6eb2a05cee8e55c6871b',
];
import { isAdminPubkey } from '@/lib/admins';
// ─── Newsletter issue structure ───────────────────────────────────────────────
//

View file

@ -54,7 +54,9 @@ import {
Star,
Flag,
MoreHorizontal,
MessageCircle,
} from 'lucide-react';
import { nip19 } from 'nostr-tools';
import { ReportDialog } from '@/components/ReportDialog';
import { WhosSkating } from '@/components/WhosSkating';
import { SetStatusDialog } from '@/components/SetStatusDialog';
@ -146,6 +148,8 @@ export default function SpotDetailPage() {
const metadata = author.data?.metadata;
const displayName = metadata?.name ?? genUserName(spot?.pubkey || '');
// npub for linking to the mapper's profile / messaging them
const mapperNpub = spot?.pubkey ? nip19.npubEncode(spot.pubkey) : '';
const coordinates = useMemo(() => {
if (!spot?.geohash) return null;
@ -910,7 +914,10 @@ export default function SpotDetailPage() {
</CardTitle>
</CardHeader>
<CardContent>
<div className="flex items-center gap-3 mb-4">
<Link
to={`/${mapperNpub}`}
className="flex items-center gap-3 mb-4 group rounded-xl -m-1 p-1 hover:bg-white/5 transition-colors"
>
<Avatar className="h-12 w-12 border-2 border-[#ccff00]/30">
<AvatarImage src={metadata?.picture} alt={displayName} />
<AvatarFallback className="bg-[#ccff00]/10 text-[#ccff00]">
@ -918,22 +925,40 @@ export default function SpotDetailPage() {
</AvatarFallback>
</Avatar>
<div className="flex-1 min-w-0">
<p className="font-medium truncate">{displayName}</p>
<p className="font-medium truncate group-hover:text-[#ccff00] transition-colors">
{displayName}
</p>
<p className="text-sm text-muted-foreground flex items-center gap-1">
<Calendar className="h-3 w-3" />
{new Date(spot.createdAt * 1000).toLocaleDateString()}
</p>
</div>
</div>
</Link>
<p className="text-xs text-gray-400 mb-3">
Found this spot useful? Send a zap to thank the mapper!
</p>
<ZapButton
target={spot.event}
className="w-full justify-center bg-[#ccff00] text-black hover:bg-[#ccff00]/90 font-bold"
/>
<div className="space-y-2">
<ZapButton
target={spot.event}
className="w-full justify-center bg-[#ccff00] text-black hover:bg-[#ccff00]/90 font-bold"
/>
{/* Message the mapper — e.g. to ask them to add photos.
Hidden when you're the owner (can't message yourself). */}
{!isOwner && (
<Button
asChild
variant="outline"
className="w-full justify-center border-[#ccff00]/40 text-[#ccff00] hover:bg-[#ccff00]/10 hover:text-[#ccff00] font-bold"
>
<Link to={`/messages/${mapperNpub}`}>
<MessageCircle className="h-4 w-4 mr-2" />
Message Mapper
</Link>
</Button>
)}
</div>
</CardContent>
</Card>
</div>