import { useState, useEffect } from 'react'; import { Link, useLocation } from 'react-router-dom'; 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 ]; /** * How many days the "Latest Drop" pill stays visible after a new issue. * After this window it auto-hides until the next issue drops. */ const PILL_VISIBLE_DAYS = 7; /** * Pre-filled Shakespeare chat URL that opens ready to write the next newsletter. * When you click it on a Monday, it takes you straight into the AI with the * prompt already typed — just hit send. */ const SHAKESPEARE_PROMPT = encodeURIComponent( 'Write this week\'s SkateSpots newsletter issue. Read the git log for the past 7 days, summarize all changes in plain skater language, pick a spot of the week if any new ones were submitted, and format it following the same structure as Issue #001. Keep it short, punchy, no corporate tone.' ); const WRITE_NEWSLETTER_URL = `https://shakespeare.diy/project/skatespots?prompt=${SHAKESPEARE_PROMPT}`; // ─── Helpers ───────────────────────────────────────────────────────────────── function isMonday(): boolean { return new Date().getDay() === 1; } function daysSinceIssue(isoDate: string): number { const issued = new Date(isoDate).getTime(); const now = Date.now(); return Math.floor((now - issued) / (1000 * 60 * 60 * 24)); } function formatIssueNumber(n: number): string { return `#${String(n).padStart(3, '0')}`; } // ─── Floating "Latest Drop" pill ───────────────────────────────────────────── function LatestDropPill() { const location = useLocation(); const latest = NEWSLETTER_ISSUES[0]; const age = daysSinceIssue(latest.date); const [dismissed, setDismissed] = useLocalStorage( 'newsletter-pill-dismissed-issue', 0 ); // Hide on newsletter page, map page (to avoid conflicts with map UI), if too old, or if already dismissed const isMapPage = location.pathname === '/' || location.pathname === '/map'; if ( location.pathname === '/newsletter' || isMapPage || age > PILL_VISIBLE_DAYS || dismissed === latest.number ) { return null; } return (
Drop {formatIssueNumber(latest.number)} is out · {age === 0 ? 'Today' : age === 1 ? 'Yesterday' : `${age}d ago`}
); } // ─── Monday admin reminder ──────────────────────────────────────────────────── function MondayAdminReminder() { const { user } = useCurrentUser(); const location = useLocation(); const [dismissed, setDismissed] = useLocalStorage( 'newsletter-monday-dismissed', '' ); // 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 alreadyDismissedToday = dismissed === todayKey; if ( !isAdmin || !isMonday() || alreadyDismissedToday || location.pathname === '/newsletter' ) { return null; } return (

It's Monday

Time to drop the weekly newsletter. One click and the AI writes it from the git log.

setDismissed(todayKey)} > Write Drop #{String(NEWSLETTER_ISSUES[0].number + 1).padStart(3, '0')}
); } // ─── Combined overlay export ────────────────────────────────────────────────── export function NewsletterOverlay() { const [mounted, setMounted] = useState(false); // Avoid SSR hydration mismatch — only render client-side useEffect(() => { setMounted(true); }, []); if (!mounted) return null; return ( <> ); }